Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2019-06-28 16:16:06 -07:00
commit 3496336f46
58 changed files with 831 additions and 584 deletions

View File

@ -53,6 +53,12 @@ Bugfixes:
- GBA Hardware: Fix RTC overriding light sensor (fixes mgba.io/i/1069) - GBA Hardware: Fix RTC overriding light sensor (fixes mgba.io/i/1069)
- GBA Savedata: Fix savedata modified time updating when read-only - GBA Savedata: Fix savedata modified time updating when read-only
- GB Video: Fix enabling window when LY > WY (fixes mgba.io/i/409) - GB Video: Fix enabling window when LY > WY (fixes mgba.io/i/409)
- GBA Video: Start timing mid-scanline when skipping BIOS
- Core: Fix audio sync breaking when interrupted
- Qt: Improve FPS timer stability
- GBA Serialize: Fix loading channel 3 volume (fixes mgba.io/i/1107)
- GBA SIO: Fix unconnected SIOCNT for multi mode (fixes mgba.io/i/1105)
- GBA BIOS: Fix BitUnPack final byte
Misc: Misc:
- GBA Timer: Use global cycles for timers - GBA Timer: Use global cycles for timers
- GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722)
@ -77,6 +83,7 @@ Misc:
- Qt: Add load alternate save option - Qt: Add load alternate save option
- GB Audio: Improved audio quality - GB Audio: Improved audio quality
- GB, GBA Audio: Increase max audio volume - GB, GBA Audio: Increase max audio volume
- GB: Fix VRAM/palette locking (fixes mgba.io/i/1109)
0.6.3: (2017-04-14) 0.6.3: (2017-04-14)
Bugfixes: Bugfixes:

View File

@ -909,10 +909,6 @@ endif()
if(BUILD_SDL) if(BUILD_SDL)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/sdl ${CMAKE_CURRENT_BINARY_DIR}/sdl) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/sdl ${CMAKE_CURRENT_BINARY_DIR}/sdl)
# The SDL platform CMakeLists could decide to disable SDL, so check again before adding the define.
if(BUILD_SDL)
add_definitions(-DBUILD_SDL)
endif()
endif() endif()
if(BUILD_QT) if(BUILD_QT)
@ -967,7 +963,7 @@ if(BUILD_EXAMPLE)
target_link_libraries(${BINARY_NAME}-example-server ${BINARY_NAME}) target_link_libraries(${BINARY_NAME}-example-server ${BINARY_NAME})
set_target_properties(${BINARY_NAME}-example-server PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") set_target_properties(${BINARY_NAME}-example-server PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
if(BUILD_SDL) if(FOUND_SDL)
add_executable(${BINARY_NAME}-example-client ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/example/client-server/client.c) add_executable(${BINARY_NAME}-example-client ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/example/client-server/client.c)
target_link_libraries(${BINARY_NAME}-example-client ${BINARY_NAME} ${SDL_LIBRARY} ${SDLMAIN_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) target_link_libraries(${BINARY_NAME}-example-client ${BINARY_NAME} ${SDL_LIBRARY} ${SDLMAIN_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
set_target_properties(${BINARY_NAME}-example-client PROPERTIES set_target_properties(${BINARY_NAME}-example-client PROPERTIES
@ -1071,12 +1067,12 @@ if(BUILD_QT)
cpack_add_component(${BINARY_NAME}-qt GROUP qt DEPENDS base) cpack_add_component(${BINARY_NAME}-qt GROUP qt DEPENDS base)
endif() endif()
if(BUILD_SDL) if(FOUND_SDL)
cpack_add_component_group(sdl PARENT_GROUP base) cpack_add_component_group(sdl PARENT_GROUP base)
cpack_add_component(${BINARY_NAME}-sdl GROUP sdl DEPENDS base) cpack_add_component(${BINARY_NAME}-sdl GROUP sdl DEPENDS base)
endif() endif()
cpack_add_component_group($test PARENT_GROUP dev) cpack_add_component_group(test PARENT_GROUP dev)
cpack_add_component(${BINARY_NAME}-perf GROUP test DEPENDS dev) cpack_add_component(${BINARY_NAME}-perf GROUP test DEPENDS dev)
cpack_add_component(${BINARY_NAME}-fuzz GROUP test DEPENDS dev) cpack_add_component(${BINARY_NAME}-fuzz GROUP test DEPENDS dev)
cpack_add_component(tbl-fuzz GROUP test DEPENDS dev) cpack_add_component(tbl-fuzz GROUP test DEPENDS dev)

View File

@ -140,7 +140,7 @@ This will build and install medusa into `/usr/bin` and `/usr/lib`. Dependencies
If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are: If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are:
brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit pkg-config
mkdir build mkdir build
cd build cd build
cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` .. cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..

View File

@ -122,7 +122,7 @@ Damit wird mGBA gebaut und in `/usr/bin` und `/usr/lib` installiert. Installiert
Wenn Du macOS verwendest, sind die einzelnen Schritte etwas anders. Angenommen, dass Du eine Homebrew-Paketverwaltung verwendest, werden folgende Schritte zum installieren der Abhängigkeiten und anschließenden bauen von mGBA empfohlen: Wenn Du macOS verwendest, sind die einzelnen Schritte etwas anders. Angenommen, dass Du eine Homebrew-Paketverwaltung verwendest, werden folgende Schritte zum installieren der Abhängigkeiten und anschließenden bauen von mGBA empfohlen:
brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit pkg-config
mkdir build mkdir build
cd build cd build
cmake -DCMAKE_PREFIX_PATH='brew --prefix qt5' .. cmake -DCMAKE_PREFIX_PATH='brew --prefix qt5' ..

View File

@ -136,12 +136,13 @@ typedef intptr_t ssize_t;
uint32_t lo; \ uint32_t lo; \
}; \ }; \
uint64_t b64; \ uint64_t b64; \
} *bswap = (void*) &DEST; \ } bswap; \
const void* _ptr = (ARR); \ const void* _ptr = (ARR); \
__asm__( \ __asm__( \
"lwbrx %0, %2, %3 \n" \ "lwbrx %0, %2, %3 \n" \
"lwbrx %1, %2, %4 \n" \ "lwbrx %1, %2, %4 \n" \
: "=&r"(bswap->lo), "=&r"(bswap->hi) : "b"(_ptr), "r"(_addr), "r"(_addr + 4)) ; \ : "=&r"(bswap.lo), "=&r"(bswap.hi) : "b"(_ptr), "r"(_addr), "r"(_addr + 4)) ; \
DEST = bswap.b64; \
} }
#define STORE_64LE(SRC, ADDR, ARR) { \ #define STORE_64LE(SRC, ADDR, ARR) { \
@ -152,12 +153,12 @@ typedef intptr_t ssize_t;
uint32_t lo; \ uint32_t lo; \
}; \ }; \
uint64_t b64; \ uint64_t b64; \
} *bswap = (void*) &SRC; \ } bswap = { .b64 = SRC }; \
const void* _ptr = (ARR); \ const void* _ptr = (ARR); \
__asm__( \ __asm__( \
"stwbrx %0, %2, %3 \n" \ "stwbrx %0, %2, %3 \n" \
"stwbrx %1, %2, %4 \n" \ "stwbrx %1, %2, %4 \n" \
: : "r"(bswap->hi), "r"(bswap->lo), "b"(_ptr), "r"(_addr), "r"(_addr + 4) : "memory"); \ : : "r"(bswap.hi), "r"(bswap.lo), "b"(_ptr), "r"(_addr), "r"(_addr + 4) : "memory"); \
} }
#elif defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) #elif defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)

View File

@ -33,7 +33,8 @@ bool mCoreSyncWaitFrameStart(struct mCoreSync* sync);
void mCoreSyncWaitFrameEnd(struct mCoreSync* sync); void mCoreSyncWaitFrameEnd(struct mCoreSync* sync);
void mCoreSyncSetVideoSync(struct mCoreSync* sync, bool wait); void mCoreSyncSetVideoSync(struct mCoreSync* sync, bool wait);
void mCoreSyncProduceAudio(struct mCoreSync* sync, bool wait); struct blip_t;
bool mCoreSyncProduceAudio(struct mCoreSync* sync, const struct blip_t*, size_t samples);
void mCoreSyncLockAudio(struct mCoreSync* sync); void mCoreSyncLockAudio(struct mCoreSync* sync);
void mCoreSyncUnlockAudio(struct mCoreSync* sync); void mCoreSyncUnlockAudio(struct mCoreSync* sync);
void mCoreSyncConsumeAudio(struct mCoreSync* sync); void mCoreSyncConsumeAudio(struct mCoreSync* sync);

View File

@ -15,7 +15,7 @@
#cmakedefine BUILD_GLES2 #cmakedefine BUILD_GLES2
#endif #endif
// COLOR flags // Miscellaneous flags
#ifndef COLOR_16_BIT #ifndef COLOR_16_BIT
#cmakedefine COLOR_16_BIT #cmakedefine COLOR_16_BIT
@ -25,6 +25,14 @@
#cmakedefine COLOR_5_6_5 #cmakedefine COLOR_5_6_5
#endif #endif
#ifndef DISABLE_THREADING
#cmakedefine DISABLE_THREADING
#endif
#ifndef FIXED_ROM_BUFFER
#cmakedefine FIXED_ROM_BUFFER
#endif
// M_CORE flags // M_CORE flags
#ifndef M_CORE_GBA #ifndef M_CORE_GBA
@ -39,9 +47,13 @@
#cmakedefine M_CORE_DS #cmakedefine M_CORE_DS
#endif #endif
// USE flags // ENABLE flags
#ifndef MINIMAL_CORE #ifndef ENABLE_SCRIPTING
#cmakedefine ENABLE_SCRIPTING
#endif
// USE flags
#ifndef USE_DEBUGGERS #ifndef USE_DEBUGGERS
#cmakedefine USE_DEBUGGERS #cmakedefine USE_DEBUGGERS
@ -51,6 +63,10 @@
#cmakedefine USE_EDITLINE #cmakedefine USE_EDITLINE
#endif #endif
#ifndef USE_ELF
#cmakedefine USE_ELF
#endif
#ifndef USE_EPOXY #ifndef USE_EPOXY
#cmakedefine USE_EPOXY #cmakedefine USE_EPOXY
#endif #endif
@ -100,5 +116,3 @@
#endif #endif
#endif #endif
#endif

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/core/sync.h> #include <mgba/core/sync.h>
#include <mgba/core/blip_buf.h>
static void _changeVideoSync(struct mCoreSync* sync, bool frameOn) { static void _changeVideoSync(struct mCoreSync* sync, bool frameOn) {
// Make sure the video thread can process events while the GBA thread is paused // Make sure the video thread can process events while the GBA thread is paused
MutexLock(&sync->videoFrameMutex); MutexLock(&sync->videoFrameMutex);
@ -76,16 +78,20 @@ void mCoreSyncSetVideoSync(struct mCoreSync* sync, bool wait) {
_changeVideoSync(sync, wait); _changeVideoSync(sync, wait);
} }
void mCoreSyncProduceAudio(struct mCoreSync* sync, bool wait) { bool mCoreSyncProduceAudio(struct mCoreSync* sync, const struct blip_t* buf, size_t samples) {
if (!sync) { if (!sync) {
return; return true;
} }
if (sync->audioWait && wait) { size_t produced = blip_samples_avail(buf);
// TODO loop properly in event of spurious wakeups size_t producedNew = produced;
while (sync->audioWait && producedNew >= samples) {
ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex); ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
produced = producedNew;
producedNew = blip_samples_avail(buf);
} }
MutexUnlock(&sync->audioBufferMutex); MutexUnlock(&sync->audioBufferMutex);
return producedNew != produced;
} }
void mCoreSyncLockAudio(struct mCoreSync* sync) { void mCoreSyncLockAudio(struct mCoreSync* sync) {

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/core/thread.h> #include <mgba/core/thread.h>
#include <mgba/core/blip_buf.h>
#include <mgba/core/core.h> #include <mgba/core/core.h>
#include <mgba/core/serialize.h> #include <mgba/core/serialize.h>
#include <mgba-util/patch.h> #include <mgba-util/patch.h>
@ -219,6 +220,11 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
deferred = impl->state; deferred = impl->state;
while (impl->state >= THREAD_WAITING && impl->state <= THREAD_MAX_WAITING) { while (impl->state >= THREAD_WAITING && impl->state <= THREAD_MAX_WAITING) {
ConditionWait(&impl->stateCond, &impl->stateMutex); ConditionWait(&impl->stateCond, &impl->stateMutex);
if (impl->sync.audioWait) {
mCoreSyncLockAudio(&impl->sync);
mCoreSyncProduceAudio(&impl->sync, core->getAudioChannel(core, 0), core->getAudioBufferSize(core));
}
} }
} }
MutexUnlock(&impl->stateMutex); MutexUnlock(&impl->stateMutex);

View File

@ -352,7 +352,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight); audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
} }
bool wait = produced >= audio->samples; bool wait = produced >= audio->samples;
mCoreSyncProduceAudio(audio->p->sync, wait); mCoreSyncProduceAudio(audio->p->sync, audio->left, wait);
if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) { if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) {
audio->p->stream->postAudioBuffer(audio->p->stream, audio->left, audio->right); audio->p->stream->postAudioBuffer(audio->p->stream, audio->left, audio->right);

View File

@ -667,7 +667,10 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight); audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
} }
bool wait = produced >= audio->samples; bool wait = produced >= audio->samples;
mCoreSyncProduceAudio(audio->p->sync, wait); if (!mCoreSyncProduceAudio(audio->p->sync, audio->left, audio->samples)) {
// Interrupted
audio->p->earlyExit = true;
}
if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) { if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) {
audio->p->stream->postAudioBuffer(audio->p->stream, audio->left, audio->right); audio->p->stream->postAudioBuffer(audio->p->stream, audio->left, audio->right);

View File

@ -644,17 +644,17 @@ void GBProcessEvents(struct LR35902Core* cpu) {
} while (gb->cpuBlocked); } while (gb->cpuBlocked);
cpu->nextEvent = nextEvent; cpu->nextEvent = nextEvent;
if (gb->earlyExit) {
gb->earlyExit = false;
break;
}
if (cpu->halted) { if (cpu->halted) {
cpu->cycles = cpu->nextEvent; cpu->cycles = cpu->nextEvent;
if (!gb->memory.ie || !gb->memory.ime) { if (!gb->memory.ie || !gb->memory.ime) {
break; break;
} }
} }
if (gb->earlyExit) {
break;
}
} while (cpu->cycles >= cpu->nextEvent); } while (cpu->cycles >= cpu->nextEvent);
gb->earlyExit = false;
} }
void GBSetInterrupts(struct LR35902Core* cpu, bool enable) { void GBSetInterrupts(struct LR35902Core* cpu, bool enable) {

View File

@ -104,6 +104,8 @@ static const uint8_t _registerMask[] = {
[REG_IE] = 0xE0, [REG_IE] = 0xE0,
}; };
static uint8_t _readKeys(struct GB* gb);
static void _writeSGBBits(struct GB* gb, int bits) { static void _writeSGBBits(struct GB* gb, int bits) {
if (!bits) { if (!bits) {
gb->sgbBit = -1; gb->sgbBit = -1;
@ -394,10 +396,12 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
} }
break; break;
case REG_JOYP: case REG_JOYP:
gb->memory.io[REG_JOYP] = value | 0x0F;
_readKeys(gb);
if (gb->model == GB_MODEL_SGB) { if (gb->model == GB_MODEL_SGB) {
_writeSGBBits(gb, (value >> 4) & 3); _writeSGBBits(gb, (value >> 4) & 3);
} }
break; return;
case REG_TIMA: case REG_TIMA:
if (value && mTimingUntil(&gb->timing, &gb->timer.irq) > 1) { if (value && mTimingUntil(&gb->timing, &gb->timer.irq) > 1) {
mTimingDeschedule(&gb->timing, &gb->timer.irq); mTimingDeschedule(&gb->timing, &gb->timer.irq);
@ -485,8 +489,10 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
gb->memory.io[REG_BCPD] = gb->video.palette[gb->video.bcpIndex >> 1] >> (8 * (gb->video.bcpIndex & 1)); gb->memory.io[REG_BCPD] = gb->video.palette[gb->video.bcpIndex >> 1] >> (8 * (gb->video.bcpIndex & 1));
break; break;
case REG_BCPD: case REG_BCPD:
GBVideoProcessDots(&gb->video, 0); if (gb->video.mode != 3) {
GBVideoWritePalette(&gb->video, address, value); GBVideoProcessDots(&gb->video, 0);
GBVideoWritePalette(&gb->video, address, value);
}
return; return;
case REG_OCPS: case REG_OCPS:
gb->video.ocpIndex = value & 0x3F; gb->video.ocpIndex = value & 0x3F;
@ -494,8 +500,10 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
gb->memory.io[REG_OCPD] = gb->video.palette[8 * 4 + (gb->video.ocpIndex >> 1)] >> (8 * (gb->video.ocpIndex & 1)); gb->memory.io[REG_OCPD] = gb->video.palette[8 * 4 + (gb->video.ocpIndex >> 1)] >> (8 * (gb->video.ocpIndex & 1));
break; break;
case REG_OCPD: case REG_OCPD:
GBVideoProcessDots(&gb->video, 0); if (gb->video.mode != 3) {
GBVideoWritePalette(&gb->video, address, value); GBVideoProcessDots(&gb->video, 0);
GBVideoWritePalette(&gb->video, address, value);
}
return; return;
case REG_SVBK: case REG_SVBK:
GBMemorySwitchWramBank(&gb->memory, value); GBMemorySwitchWramBank(&gb->memory, value);
@ -522,7 +530,8 @@ static uint8_t _readKeys(struct GB* gb) {
if (gb->sgbCurrentController != 0) { if (gb->sgbCurrentController != 0) {
keys = 0; keys = 0;
} }
switch (gb->memory.io[REG_JOYP] & 0x30) { uint8_t joyp = gb->memory.io[REG_JOYP];
switch (joyp & 0x30) {
case 0x30: case 0x30:
keys = gb->sgbCurrentController; keys = gb->sgbCurrentController;
break; break;
@ -535,7 +544,12 @@ static uint8_t _readKeys(struct GB* gb) {
keys |= keys >> 4; keys |= keys >> 4;
break; break;
} }
return (0xC0 | (gb->memory.io[REG_JOYP] | 0xF)) ^ (keys & 0xF); gb->memory.io[REG_JOYP] = (0xCF | joyp) ^ (keys & 0xF);
if (joyp & ~gb->memory.io[REG_JOYP] & 0xF) {
gb->memory.io[REG_IF] |= (1 << GB_IRQ_KEYPAD);
GBUpdateIRQs(gb);
}
return gb->memory.io[REG_JOYP];
} }
uint8_t GBIORead(struct GB* gb, unsigned address) { uint8_t GBIORead(struct GB* gb, unsigned address) {
@ -639,10 +653,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
} }
void GBTestKeypadIRQ(struct GB* gb) { void GBTestKeypadIRQ(struct GB* gb) {
if (_readKeys(gb)) { _readKeys(gb);
gb->memory.io[REG_IF] |= (1 << GB_IRQ_KEYPAD);
GBUpdateIRQs(gb);
}
} }
struct GBSerializedState; struct GBSerializedState;

View File

@ -239,7 +239,10 @@ uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) {
return memory->romBank[address & (GB_SIZE_CART_BANK0 - 1)]; return memory->romBank[address & (GB_SIZE_CART_BANK0 - 1)];
case GB_REGION_VRAM: case GB_REGION_VRAM:
case GB_REGION_VRAM + 1: case GB_REGION_VRAM + 1:
return gb->video.vramBank[address & (GB_SIZE_VRAM_BANK0 - 1)]; if (gb->video.mode != 3) {
return gb->video.vramBank[address & (GB_SIZE_VRAM_BANK0 - 1)];
}
return 0xFF;
case GB_REGION_EXTERNAL_RAM: case GB_REGION_EXTERNAL_RAM:
case GB_REGION_EXTERNAL_RAM + 1: case GB_REGION_EXTERNAL_RAM + 1:
if (memory->rtcAccess) { if (memory->rtcAccess) {
@ -309,8 +312,10 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) {
return; return;
case GB_REGION_VRAM: case GB_REGION_VRAM:
case GB_REGION_VRAM + 1: case GB_REGION_VRAM + 1:
gb->video.renderer->writeVRAM(gb->video.renderer, (address & (GB_SIZE_VRAM_BANK0 - 1)) | (GB_SIZE_VRAM_BANK0 * gb->video.vramCurrentBank)); if (gb->video.mode != 3) {
gb->video.vramBank[address & (GB_SIZE_VRAM_BANK0 - 1)] = value; gb->video.renderer->writeVRAM(gb->video.renderer, (address & (GB_SIZE_VRAM_BANK0 - 1)) | (GB_SIZE_VRAM_BANK0 * gb->video.vramCurrentBank));
gb->video.vramBank[address & (GB_SIZE_VRAM_BANK0 - 1)] = value;
}
return; return;
case GB_REGION_EXTERNAL_RAM: case GB_REGION_EXTERNAL_RAM:
case GB_REGION_EXTERNAL_RAM + 1: case GB_REGION_EXTERNAL_RAM + 1:
@ -490,7 +495,7 @@ uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) {
gb->memory.hdmaDest |= 0x8000; gb->memory.hdmaDest |= 0x8000;
bool wasHdma = gb->memory.isHdma; bool wasHdma = gb->memory.isHdma;
gb->memory.isHdma = value & 0x80; gb->memory.isHdma = value & 0x80;
if ((!wasHdma && !gb->memory.isHdma) || gb->video.mode == 0) { if ((!wasHdma && !gb->memory.isHdma) || (GBRegisterLCDCIsEnable(gb->memory.io[REG_LCDC]) && gb->video.mode == 0)) {
if (gb->memory.isHdma) { if (gb->memory.isHdma) {
gb->memory.hdmaRemaining = 0x10; gb->memory.hdmaRemaining = 0x10;
} else { } else {

View File

@ -12,7 +12,12 @@
#include <mgba-util/crc32.h> #include <mgba-util/crc32.h>
static const struct GBCartridgeOverride _overrides[] = { static const struct GBCartridgeOverride _overrides[] = {
// None yet // Pokemon Spaceworld 1997 demo
{ 0x232a067d, GB_MODEL_AUTODETECT, GB_MBC3_RTC, { 0 } }, // Gold (debug)
{ 0x630ed957, GB_MODEL_AUTODETECT, GB_MBC3_RTC, { 0 } }, // Gold (non-debug)
{ 0x5aff0038, GB_MODEL_AUTODETECT, GB_MBC3_RTC, { 0 } }, // Silver (debug)
{ 0xa61856bd, GB_MODEL_AUTODETECT, GB_MBC3_RTC, { 0 } }, // Silver (non-debug)
{ 0, 0, 0, { 0 } } { 0, 0, 0, { 0 } }
}; };

View File

@ -147,7 +147,7 @@ void GBVideoReset(struct GBVideo* video) {
} }
void GBVideoDeinit(struct GBVideo* video) { void GBVideoDeinit(struct GBVideo* video) {
GBVideoAssociateRenderer(video, &dummyRenderer); video->renderer->deinit(video->renderer);
mappedMemoryFree(video->vram, GB_SIZE_VRAM); mappedMemoryFree(video->vram, GB_SIZE_VRAM);
if (video->renderer->sgbCharRam) { if (video->renderer->sgbCharRam) {
mappedMemoryFree(video->renderer->sgbCharRam, SGB_SIZE_CHAR_RAM); mappedMemoryFree(video->renderer->sgbCharRam, SGB_SIZE_CHAR_RAM);
@ -437,6 +437,7 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) {
} }
if (GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && !GBRegisterLCDCIsEnable(value)) { if (GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && !GBRegisterLCDCIsEnable(value)) {
// TODO: Fix serialization; this gets internal and visible modes out of sync // TODO: Fix serialization; this gets internal and visible modes out of sync
video->mode = 0;
video->stat = GBRegisterSTATSetMode(video->stat, 0); video->stat = GBRegisterSTATSetMode(video->stat, 0);
video->p->memory.io[REG_STAT] = video->stat; video->p->memory.io[REG_STAT] = video->stat;
video->ly = 0; video->ly = 0;

View File

@ -307,7 +307,10 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight); audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
} }
bool wait = produced >= audio->samples; bool wait = produced >= audio->samples;
mCoreSyncProduceAudio(audio->p->sync, wait); if (!mCoreSyncProduceAudio(audio->p->sync, audio->psg.left, audio->samples)) {
// Interrupted
audio->p->earlyExit = true;
}
if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) { if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) {
audio->p->stream->postAudioBuffer(audio->p->stream, audio->psg.left, audio->psg.right); audio->p->stream->postAudioBuffer(audio->p->stream, audio->psg.left, audio->psg.right);

View File

@ -809,7 +809,7 @@ static void _unBitPack(struct GBA* gba) {
uint32_t out = 0; uint32_t out = 0;
int bitsRemaining = 0; int bitsRemaining = 0;
int bitsEaten = 0; int bitsEaten = 0;
while (sourceLen > 0) { while (sourceLen > 0 || bitsRemaining) {
if (!bitsRemaining) { if (!bitsRemaining) {
in = cpu->memory.load8(cpu, source, 0); in = cpu->memory.load8(cpu, source, 0);
bitsRemaining = 8; bitsRemaining = 8;

View File

@ -264,11 +264,6 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
} while (gba->cpuBlocked); } while (gba->cpuBlocked);
cpu->nextEvent = nextEvent; cpu->nextEvent = nextEvent;
if (gba->earlyExit) {
gba->earlyExit = false;
break;
}
if (cpu->halted) { if (cpu->halted) {
cpu->cycles = nextEvent; cpu->cycles = nextEvent;
if (!gba->memory.io[REG_IME >> 1] || !gba->memory.io[REG_IE >> 1]) { if (!gba->memory.io[REG_IME >> 1] || !gba->memory.io[REG_IE >> 1]) {
@ -280,7 +275,11 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
mLOG(GBA, FATAL, "Negative cycles will pass: %i", nextEvent); mLOG(GBA, FATAL, "Negative cycles will pass: %i", nextEvent);
} }
#endif #endif
if (gba->earlyExit) {
break;
}
} }
gba->earlyExit = false;
#ifndef NDEBUG #ifndef NDEBUG
if (gba->cpuBlocked) { if (gba->cpuBlocked) {
mLOG(GBA, FATAL, "CPU is blocked!"); mLOG(GBA, FATAL, "CPU is blocked!");

View File

@ -293,7 +293,7 @@ static const int _isWSpecialRegister[REG_MAX >> 1] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// Audio // Audio
1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0,
1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,

View File

@ -163,6 +163,9 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
value &= ~0x0080; value &= ~0x0080;
} }
break; break;
case SIO_MULTI:
value |= 0xC;
break;
default: default:
// TODO // TODO
break; break;

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau /* Copyright (c) 2013-2018 Jeffrey Pfau
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -12,6 +12,8 @@
#define TIMER_RELOAD_DELAY 0 #define TIMER_RELOAD_DELAY 0
#define TIMER_STARTUP_DELAY 2 #define TIMER_STARTUP_DELAY 2
#define REG_TMCNT_LO(X) (REG_TM0CNT_LO + ((X) << 2))
static void GBATimerIrq(struct GBA* gba, int timerId) { static void GBATimerIrq(struct GBA* gba, int timerId) {
struct GBATimer* timer = &gba->timers[timerId]; struct GBATimer* timer = &gba->timers[timerId];
if (GBATimerFlagsIsIrqPending(timer->flags)) { if (GBATimerFlagsIsIrqPending(timer->flags)) {
@ -45,12 +47,11 @@ static void GBATimerIrq3(struct mTiming* timing, void* context, uint32_t cyclesL
} }
void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) { void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) {
*io = timer->reload; if (GBATimerFlagsIsCountUp(timer->flags)) {
int32_t currentTime = mTimingCurrentTime(timing) - cyclesLate; *io = timer->reload;
int32_t tickMask = (1 << GBATimerFlagsGetPrescaleBits(timer->flags)) - 1; } else {
currentTime &= ~tickMask; GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_RELOAD_DELAY + cyclesLate);
timer->lastEvent = currentTime; }
GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_RELOAD_DELAY + cyclesLate);
if (GBATimerFlagsIsDoIrq(timer->flags)) { if (GBATimerFlagsIsDoIrq(timer->flags)) {
timer->flags = GBATimerFlagsFillIrqPending(timer->flags); timer->flags = GBATimerFlagsFillIrqPending(timer->flags);
@ -159,20 +160,30 @@ void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timi
return; return;
} }
// Align timer
int prescaleBits = GBATimerFlagsGetPrescaleBits(timer->flags); int prescaleBits = GBATimerFlagsGetPrescaleBits(timer->flags);
int32_t currentTime = mTimingCurrentTime(timing) - skew; int32_t currentTime = mTimingCurrentTime(timing) - skew;
int32_t tickMask = (1 << prescaleBits) - 1; int32_t tickMask = (1 << prescaleBits) - 1;
currentTime &= ~tickMask; currentTime &= ~tickMask;
// Update register
int32_t tickIncrement = currentTime - timer->lastEvent; int32_t tickIncrement = currentTime - timer->lastEvent;
timer->lastEvent = currentTime; timer->lastEvent = currentTime;
tickIncrement >>= prescaleBits; tickIncrement >>= prescaleBits;
tickIncrement += *io; tickIncrement += *io;
*io = tickIncrement; *io = tickIncrement;
if (!mTimingIsScheduled(timing, &timer->event)) { while (tickIncrement >= 0x10000) {
tickIncrement = (0x10000 - tickIncrement) << prescaleBits; tickIncrement -= 0x10000 - timer->reload;
currentTime -= mTimingCurrentTime(timing) - skew;
mTimingSchedule(timing, &timer->event, tickIncrement + currentTime);
} }
*io = tickIncrement;
// Schedule next update
tickIncrement = (0x10000 - tickIncrement) << prescaleBits;
currentTime += tickIncrement;
currentTime &= ~tickMask;
currentTime -= mTimingCurrentTime(timing);
mTimingDeschedule(timing, &timer->event);
mTimingSchedule(timing, &timer->event, currentTime);
} }
void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload) { void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload) {

View File

@ -80,16 +80,18 @@ void GBAVideoInit(struct GBAVideo* video) {
} }
void GBAVideoReset(struct GBAVideo* video) { void GBAVideoReset(struct GBAVideo* video) {
int32_t nextEvent = VIDEO_HDRAW_LENGTH;
if (video->p->memory.fullBios) { if (video->p->memory.fullBios) {
video->vcount = 0; video->vcount = 0;
} else { } else {
// TODO: Verify exact scanline hardware // TODO: Verify exact scanline on hardware
video->vcount = 0x7E; video->vcount = 0x7E;
nextEvent = 170;
} }
video->p->memory.io[REG_VCOUNT >> 1] = video->vcount; video->p->memory.io[REG_VCOUNT >> 1] = video->vcount;
video->event.callback = _startHblank; video->event.callback = _startHblank;
mTimingSchedule(&video->p->timing, &video->event, VIDEO_HDRAW_LENGTH); mTimingSchedule(&video->p->timing, &video->event, nextEvent);
video->frameCounter = 0; video->frameCounter = 0;
video->frameskipCounter = 0; video->frameskipCounter = 0;
@ -113,7 +115,7 @@ void GBAVideoReset(struct GBAVideo* video) {
} }
void GBAVideoDeinit(struct GBAVideo* video) { void GBAVideoDeinit(struct GBAVideo* video) {
GBAVideoAssociateRenderer(video, &dummyRenderer); video->renderer->deinit(video->renderer);
mappedMemoryFree(video->vram, SIZE_VRAM); mappedMemoryFree(video->vram, SIZE_VRAM);
} }

View File

@ -99,6 +99,7 @@ struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) {
void LR35902DebuggerInit(void* cpu, struct mDebuggerPlatform* platform) { void LR35902DebuggerInit(void* cpu, struct mDebuggerPlatform* platform) {
struct LR35902Debugger* debugger = (struct LR35902Debugger*) platform; struct LR35902Debugger* debugger = (struct LR35902Debugger*) platform;
debugger->cpu = cpu; debugger->cpu = cpu;
debugger->originalMemory = debugger->cpu->memory;
LR35902DebugBreakpointListInit(&debugger->breakpoints, 0); LR35902DebugBreakpointListInit(&debugger->breakpoints, 0);
LR35902DebugWatchpointListInit(&debugger->watchpoints, 0); LR35902DebugWatchpointListInit(&debugger->watchpoints, 0);
} }

View File

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

View File

@ -11,15 +11,6 @@ endforeach()
file(GLOB PYTHON_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.h) 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 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 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 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}") set_target_properties(${BINARY_NAME}-pylib PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
add_custom_target(${BINARY_NAME}-py ALL 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}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py build -b ${CMAKE_CURRENT_BINARY_DIR}
COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build -b ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${BINARY_NAME} DEPENDS ${BINARY_NAME}
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/setup.py DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/setup.py
DEPENDS ${PYTHON_HEADERS} DEPENDS ${PYTHON_HEADERS}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py
DEPENDS ${BINARY_NAME}-pylib) 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 BASE_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/test_*.py)
file(GLOB SUBTESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/*/test_*.py) file(GLOB SUBTESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/*/test_*.py)
foreach(TEST IN LISTS BASE_TESTS SUBTESTS) foreach(TEST IN LISTS BASE_TESTS SUBTESTS)
@ -56,6 +61,8 @@ foreach(TEST IN LISTS BASE_TESTS SUBTESTS)
endif() endif()
string(REGEX REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/(tests/.*/)?test_" "" TEST_NAME "${TEST}") string(REGEX REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/(tests/.*/)?test_" "" TEST_NAME "${TEST}")
string(REPLACE ".py" "" TEST_NAME "${TEST_NAME}") 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}) add_test(NAME python-${TEST_NAME}
set_tests_properties(python-${TEST_NAME} PROPERTIES ENVIRONMENT "${PATH}=${CMAKE_CURRENT_BINARY_DIR}/..") 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() endforeach()

View File

@ -80,10 +80,6 @@ ffi.embedding_api('\n'.join(lines))
ffi.embedding_init_code(""" ffi.embedding_init_code("""
import os, os.path 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 from mgba._pylib import ffi, lib
symbols = {} symbols = {}
globalSyms = { globalSyms = {
@ -111,7 +107,7 @@ ffi.embedding_init_code("""
from mgba.vfs import VFile from mgba.vfs import VFile
vf = VFile(vf) vf = VFile(vf)
name = ffi.string(name) name = ffi.string(name)
source = vf.readAll().decode('utf-8') source = vf.read_all().decode('utf-8')
try: try:
code = compile(source, name, 'exec') code = compile(source, name, 'exec')
pendingCode.append(code) pendingCode.append(code)

View File

@ -2,15 +2,16 @@ from PIL.ImageChops import difference
from PIL.ImageOps import autocontrast from PIL.ImageOps import autocontrast
from PIL.Image import open as PIOpen from PIL.Image import open as PIOpen
class VideoFrame(object): class VideoFrame(object):
def __init__(self, pilImage): def __init__(self, pil_image):
self.image = pilImage.convert('RGB') self.image = pil_image.convert('RGB')
@staticmethod @staticmethod
def diff(a, b): def diff(a, b):
diff = difference(a.image, b.image) diff = difference(a.image, b.image)
diffNormalized = autocontrast(diff) diff_normalized = autocontrast(diff)
return (VideoFrame(diff), VideoFrame(diffNormalized)) return (VideoFrame(diff), VideoFrame(diff_normalized))
@staticmethod @staticmethod
def load(path): def load(path):

View File

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

View File

@ -1,5 +1,7 @@
import os, os.path import os
import mgba.core, mgba.image import os.path
import mgba.core
import mgba.image
import cinema.movie import cinema.movie
import itertools import itertools
import glob import glob
@ -7,20 +9,21 @@ import re
import yaml import yaml
from copy import deepcopy from copy import deepcopy
from cinema import VideoFrame from cinema import VideoFrame
from cinema.util import dictMerge from cinema.util import dict_merge
class CinemaTest(object): class CinemaTest(object):
TEST = 'test.(mvl|gb|gba|nds)' TEST = 'test.(mvl|gb|gba|nds)'
def __init__(self, path, root, settings={}): def __init__(self, path, root, settings={}):
self.fullPath = path or [] self.full_path = path or []
self.path = os.path.abspath(os.path.join(root, *self.fullPath)) self.path = os.path.abspath(os.path.join(root, *self.full_path))
self.root = root self.root = root
self.name = '.'.join(path) self.name = '.'.join(path)
self.settings = settings self.settings = settings
try: try:
with open(os.path.join(self.path, 'manifest.yml'), 'r') as f: 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: except IOError:
pass pass
self.tests = {} self.tests = {}
@ -28,41 +31,42 @@ class CinemaTest(object):
def __repr__(self): def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, self.name) 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)] 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: if 'config' in self.settings:
self.config = mgba.core.Config(defaults=self.settings['config']) self.config = mgba.core.Config(defaults=self.settings['config'])
self.core.loadConfig(self.config) self.core.load_config(self.config)
self.core.reset() self.core.reset()
def addTest(self, name, cls=None, settings={}): def add_test(self, name, cls=None, settings={}):
cls = cls or self.__class__ cls = cls or self.__class__
newSettings = deepcopy(self.settings) new_settings = deepcopy(self.settings)
dictMerge(newSettings, settings) dict_merge(new_settings, settings)
self.tests[name] = cls(self.fullPath + [name], self.root, newSettings) self.tests[name] = cls(self.full_path + [name], self.root, new_settings)
return self.tests[name] return self.tests[name]
def outputSettings(self): def output_settings(self):
outputSettings = {} output_settings = {}
if 'frames' in self.settings: if 'frames' in self.settings:
outputSettings['limit'] = self.settings['frames'] output_settings['limit'] = self.settings['frames']
if 'skip' in self.settings: if 'skip' in self.settings:
outputSettings['skip'] = self.settings['skip'] output_settings['skip'] = self.settings['skip']
return outputSettings return output_settings
def __lt__(self, other): def __lt__(self, other):
return self.path < other.path return self.path < other.path
class VideoTest(CinemaTest): class VideoTest(CinemaTest):
BASELINE = 'baseline_%04u.png' BASELINE = 'baseline_%04u.png'
def setUp(self): def setup(self):
super(VideoTest, self).setUp(); super(VideoTest, self).setup()
self.tracer = cinema.movie.Tracer(self.core) self.tracer = cinema.movie.Tracer(self.core)
def generateFrames(self): def generate_frames(self):
for i, frame in zip(itertools.count(), self.tracer.video(**self.outputSettings())): for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())):
try: try:
baseline = VideoFrame.load(os.path.join(self.path, self.BASELINE % i)) baseline = VideoFrame.load(os.path.join(self.path, self.BASELINE % i))
yield baseline, frame, VideoFrame.diff(baseline, frame) yield baseline, frame, VideoFrame.diff(baseline, frame)
@ -70,14 +74,15 @@ class VideoTest(CinemaTest):
yield None, frame, (None, None) yield None, frame, (None, None)
def test(self): 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) assert not any(any(diffs[0].image.convert("L").point(bool).getdata()) for diffs in self.diffs)
def generateBaseline(self): def generate_baseline(self):
for i, frame in zip(itertools.count(), self.tracer.video(**self.outputSettings())): for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())):
frame.save(os.path.join(self.path, self.BASELINE % i)) frame.save(os.path.join(self.path, self.BASELINE % i))
def gatherTests(root=os.getcwd()):
def gather_tests(root=os.getcwd()):
tests = CinemaTest([], root) tests = CinemaTest([], root)
for path, _, files in os.walk(root): for path, _, files in os.walk(root):
test = [f for f in files if re.match(CinemaTest.TEST, f)] test = [f for f in files if re.match(CinemaTest.TEST, f)]
@ -85,12 +90,12 @@ def gatherTests(root=os.getcwd()):
continue continue
prefix = os.path.commonprefix([path, root]) prefix = os.path.commonprefix([path, root])
suffix = path[len(prefix)+1:] suffix = path[len(prefix)+1:]
testPath = suffix.split(os.sep) test_path = suffix.split(os.sep)
testRoot = tests test_root = tests
for component in testPath[:-1]: for component in test_path[:-1]:
newTest = testRoot.tests.get(component) new_test = test_root.tests.get(component)
if not newTest: if not new_test:
newTest = testRoot.addTest(component) new_test = test_root.add_test(component)
testRoot = newTest test_root = new_test
testRoot.addTest(testPath[-1], VideoTest) test_root.add_test(test_path[-1], VideoTest)
return tests return tests

View File

@ -1,8 +1,8 @@
def dictMerge(a, b): def dict_merge(a, b):
for key, value in b.items(): for key, value in b.items():
if isinstance(value, dict): if isinstance(value, dict):
if key in a: if key in a:
dictMerge(a[key], value) dict_merge(a[key], value)
else: else:
a[key] = dict(value) a[key] = dict(value)
else: else:

View File

@ -3,31 +3,34 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 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") GitInfo = namedtuple("GitInfo", "commit commitShort branch revision")
git = {} GIT = {}
if lib.gitCommit and lib.gitCommit != "(unknown)": 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)": 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)": 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: if lib.gitRevision > 0:
git['revision'] = lib.gitRevision GIT['revision'] = lib.gitRevision
git = GitInfo(**git) GIT = GitInfo(**GIT)

View File

@ -3,21 +3,23 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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: class _ARMRegisters:
def __init__(self, cpu): def __init__(self, cpu):
self._cpu = cpu self._cpu = cpu
def __getitem__(self, r): def __getitem__(self, reg):
if r > lib.ARM_PC: if reg > lib.ARM_PC:
raise IndexError("Register out of range") raise IndexError("Register out of range")
return self._cpu._native.gprs[r] return self._cpu._native.gprs[reg]
def __setitem__(self, r, value): def __setitem__(self, reg, value):
if r >= lib.ARM_PC: if reg >= lib.ARM_PC:
raise IndexError("Register out of range") raise IndexError("Register out of range")
self._cpu._native.gprs[r] = value self._cpu._native.gprs[reg] = value
class ARMCore: class ARMCore:
def __init__(self, native): def __init__(self, native):
@ -25,4 +27,3 @@ class ARMCore:
self.gprs = _ARMRegisters(self) self.gprs = _ARMRegisters(self)
self.cpsr = self._native.cpsr self.cpsr = self._native.cpsr
self.spsr = self._native.spsr self.spsr = self._native.spsr

View File

@ -3,9 +3,11 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 tile, createCallback from . import tile
from cached_property import cached_property from cached_property import cached_property
from functools import wraps
def find(path): def find(path):
core = lib.mCoreFind(path.encode('UTF-8')) core = lib.mCoreFind(path.encode('UTF-8'))
@ -13,82 +15,95 @@ def find(path):
return None return None
return Core._init(core) 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: if core == ffi.NULL:
return None return None
return Core._init(core) return Core._init(core)
def loadPath(path):
def load_path(path):
core = find(path) core = find(path)
if not core or not core.loadFile(path): if not core or not core.load_file(path):
return None return None
return core return core
def loadVF(vf):
core = findVF(vf) def load_vf(vfile):
if not core or not core.loadROM(vf): core = find_vf(vfile)
if not core or not core.load_rom(vfile):
return None return None
return core return core
def needsReset(f):
def needs_reset(func):
@wraps(func)
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
if not self._wasReset: if not self._was_reset:
raise RuntimeError("Core must be reset first") raise RuntimeError("Core must be reset first")
return f(self, *args, **kwargs) return func(self, *args, **kwargs)
return wrapper return wrapper
def protected(f):
def protected(func):
@wraps(func)
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
if self._protected: if self._protected:
raise RuntimeError("Core is protected") raise RuntimeError("Core is protected")
return f(self, *args, **kwargs) return func(self, *args, **kwargs)
return wrapper return wrapper
@ffi.def_extern()
def _mCorePythonCallbacksVideoFrameStarted(user):
context = ffi.from_handle(user)
context._videoFrameStarted()
@ffi.def_extern() @ffi.def_extern()
def _mCorePythonCallbacksVideoFrameEnded(user): def _mCorePythonCallbacksVideoFrameStarted(user): # pylint: disable=invalid-name
context = ffi.from_handle(user) context = ffi.from_handle(user)
context._videoFrameEnded() context._video_frame_started()
@ffi.def_extern() @ffi.def_extern()
def _mCorePythonCallbacksCoreCrashed(user): def _mCorePythonCallbacksVideoFrameEnded(user): # pylint: disable=invalid-name
context = ffi.from_handle(user) context = ffi.from_handle(user)
context._coreCrashed() context._video_frame_ended()
@ffi.def_extern() @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 = ffi.from_handle(user)
context._sleep() context._sleep()
class CoreCallbacks(object): class CoreCallbacks(object):
def __init__(self): def __init__(self):
self._handle = ffi.new_handle(self) self._handle = ffi.new_handle(self)
self.videoFrameStarted = [] self.video_frame_started = []
self.videoFrameEnded = [] self.video_frame_ended = []
self.coreCrashed = [] self.core_crashed = []
self.sleep = [] self.sleep = []
self.context = lib.mCorePythonCallbackCreate(self._handle) self.context = lib.mCorePythonCallbackCreate(self._handle)
def _videoFrameStarted(self): def _video_frame_started(self):
for cb in self.videoFrameStarted: for callback in self.video_frame_started:
cb() callback()
def _videoFrameEnded(self): def _video_frame_ended(self):
for cb in self.videoFrameEnded: for callback in self.video_frame_ended:
cb() callback()
def _coreCrashed(self): def _core_crashed(self):
for cb in self.coreCrashed: for callback in self.core_crashed:
cb() callback()
def _sleep(self): def _sleep(self):
for cb in self.sleep: for callback in self.sleep:
cb() callback()
class Core(object): class Core(object):
if hasattr(lib, 'PLATFORM_GBA'): if hasattr(lib, 'PLATFORM_GBA'):
@ -102,36 +117,36 @@ class Core(object):
def __init__(self, native): def __init__(self, native):
self._core = native self._core = native
self._wasReset = False self._was_reset = False
self._protected = False self._protected = False
self._callbacks = CoreCallbacks() self._callbacks = CoreCallbacks()
self._core.addCoreCallbacks(self._core, self._callbacks.context) self._core.addCoreCallbacks(self._core, self._callbacks.context)
self.config = Config(ffi.addressof(native.config)) self.config = Config(ffi.addressof(native.config))
def __del__(self): def __del__(self):
self._wasReset = False self._was_reset = False
@cached_property @cached_property
def graphicsCache(self): def graphics_cache(self):
if not self._wasReset: if not self._was_reset:
raise RuntimeError("Core must be reset first") raise RuntimeError("Core must be reset first")
return tile.CacheSet(self) return tile.CacheSet(self)
@cached_property @cached_property
def tiles(self): def tiles(self):
t = [] tiles = []
ts = ffi.addressof(self.graphicsCache.cache.tiles) native_tiles = ffi.addressof(self.graphics_cache.cache.tiles)
for i in range(lib.mTileCacheSetSize(ts)): for i in range(lib.mTileCacheSetSize(native_tiles)):
t.append(tile.TileView(lib.mTileCacheSetGetPointer(ts, i))) tiles.append(tile.TileView(lib.mTileCacheSetGetPointer(native_tiles, i)))
return t return tiles
@cached_property @cached_property
def maps(self): def maps(self):
m = [] maps = []
ms = ffi.addressof(self.graphicsCache.cache.maps) native_maps = ffi.addressof(self.graphics_cache.cache.maps)
for i in range(lib.mMapCacheSetSize(ms)): for i in range(lib.mMapCacheSetSize(native_maps)):
m.append(tile.MapView(lib.mMapCacheSetGetPointer(ms, i))) maps.append(tile.MapView(lib.mMapCacheSetGetPointer(native_maps, i)))
return m return maps
@classmethod @classmethod
def _init(cls, native): def _init(cls, native):
@ -156,73 +171,73 @@ class Core(object):
return Core(core) return Core(core)
def _load(self): 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'))) return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8')))
def isROM(self, vf): def is_rom(self, vfile):
return bool(self._core.isROM(vf.handle)) return bool(self._core.isROM(vfile.handle))
def loadROM(self, vf): def load_rom(self, vfile):
return bool(self._core.loadROM(self._core, vf.handle)) return bool(self._core.loadROM(self._core, vfile.handle))
def loadBIOS(self, vf, id=0): def load_bios(self, vfile, id=0):
return bool(self._core.loadBIOS(self._core, vf.handle, id)) return bool(self._core.loadBIOS(self._core, vfile.handle, id))
def loadSave(self, vf): def load_save(self, vfile):
return bool(self._core.loadSave(self._core, vf.handle)) return bool(self._core.loadSave(self._core, vfile.handle))
def loadTemporarySave(self, vf): def load_temporary_save(self, vfile):
return bool(self._core.loadTemporarySave(self._core, vf.handle)) return bool(self._core.loadTemporarySave(self._core, vfile.handle))
def loadPatch(self, vf): def load_patch(self, vfile):
return bool(self._core.loadPatch(self._core, vf.handle)) return bool(self._core.loadPatch(self._core, vfile.handle))
def loadConfig(self, config): def load_config(self, config):
lib.mCoreLoadForeignConfig(self._core, config._native) lib.mCoreLoadForeignConfig(self._core, config._native)
def autoloadSave(self): def autoload_save(self):
return bool(lib.mCoreAutoloadSave(self._core)) return bool(lib.mCoreAutoloadSave(self._core))
def autoloadPatch(self): def autoload_patch(self):
return bool(lib.mCoreAutoloadPatch(self._core)) return bool(lib.mCoreAutoloadPatch(self._core))
def autoloadCheats(self): def autoload_cheats(self):
return bool(lib.mCoreAutoloadCheats(self._core)) return bool(lib.mCoreAutoloadCheats(self._core))
def platform(self): def platform(self):
return self._core.platform(self._core) return self._core.platform(self._core)
def desiredVideoDimensions(self): def desired_video_dimensions(self):
width = ffi.new("unsigned*") width = ffi.new("unsigned*")
height = ffi.new("unsigned*") height = ffi.new("unsigned*")
self._core.desiredVideoDimensions(self._core, width, height) self._core.desiredVideoDimensions(self._core, width, height)
return width[0], height[0] return width[0], height[0]
def setVideoBuffer(self, image): def set_video_buffer(self, image):
self._core.setVideoBuffer(self._core, image.buffer, image.stride) self._core.setVideoBuffer(self._core, image.buffer, image.stride)
def reset(self): def reset(self):
self._core.reset(self._core) self._core.reset(self._core)
self._load() self._load()
@needsReset @needs_reset
@protected @protected
def runFrame(self): def run_frame(self):
self._core.runFrame(self._core) self._core.runFrame(self._core)
@needsReset @needs_reset
@protected @protected
def runLoop(self): def run_loop(self):
self._core.runLoop(self._core) self._core.runLoop(self._core)
@needsReset @needs_reset
def step(self): def step(self):
self._core.step(self._core) self._core.step(self._core)
@staticmethod @staticmethod
def _keysToInt(*args, **kwargs): def _keys_to_int(*args, **kwargs):
keys = 0 keys = 0
if 'raw' in kwargs: if 'raw' in kwargs:
keys = kwargs['raw'] keys = kwargs['raw']
@ -230,22 +245,25 @@ class Core(object):
keys |= 1 << key keys |= 1 << key
return keys return keys
def setKeys(self, *args, **kwargs): @protected
self._core.setKeys(self._core, self._keysToInt(*args, **kwargs)) def set_keys(self, *args, **kwargs):
self._core.setKeys(self._core, self._keys_to_int(*args, **kwargs))
def addKeys(self, *args, **kwargs): @protected
self._core.addKeys(self._core, self._keysToInt(*args, **kwargs)) def add_keys(self, *args, **kwargs):
self._core.addKeys(self._core, self._keys_to_int(*args, **kwargs))
def clearKeys(self, *args, **kwargs): @protected
self._core.clearKeys(self._core, self._keysToInt(*args, **kwargs)) def clear_keys(self, *args, **kwargs):
self._core.clearKeys(self._core, self._keys_to_int(*args, **kwargs))
@property @property
@needsReset @needs_reset
def frameCounter(self): def frame_counter(self):
return self._core.frameCounter(self._core) return self._core.frameCounter(self._core)
@property @property
def frameCycles(self): def frame_cycles(self):
return self._core.frameCycles(self._core) return self._core.frameCycles(self._core)
@property @property
@ -253,24 +271,25 @@ class Core(object):
return self._core.frequency(self._core) return self._core.frequency(self._core)
@property @property
def gameTitle(self): def game_title(self):
title = ffi.new("char[16]") title = ffi.new("char[16]")
self._core.getGameTitle(self._core, title) self._core.getGameTitle(self._core, title)
return ffi.string(title, 16).decode("ascii") return ffi.string(title, 16).decode("ascii")
@property @property
def gameCode(self): def game_code(self):
code = ffi.new("char[12]") code = ffi.new("char[12]")
self._core.getGameCode(self._core, code) self._core.getGameCode(self._core, code)
return ffi.string(code, 12).decode("ascii") return ffi.string(code, 12).decode("ascii")
def addFrameCallback(self, cb): def add_frame_callback(self, callback):
self._callbacks.videoFrameEnded.append(cb) self._callbacks.video_frame_ended.append(callback)
@property @property
def crc32(self): def crc32(self):
return self._native.romCrc32 return self._native.romCrc32
class ICoreOwner(object): class ICoreOwner(object):
def claim(self): def claim(self):
raise NotImplementedError raise NotImplementedError
@ -287,6 +306,7 @@ class ICoreOwner(object):
self.core._protected = False self.core._protected = False
self.release() self.release()
class IRunner(object): class IRunner(object):
def pause(self): def pause(self):
raise NotImplementedError raise NotImplementedError
@ -294,15 +314,18 @@ class IRunner(object):
def unpause(self): def unpause(self):
raise NotImplementedError raise NotImplementedError
def useCore(self): def use_core(self):
raise NotImplementedError raise NotImplementedError
def isRunning(self): @property
def running(self):
raise NotImplementedError raise NotImplementedError
def isPaused(self): @property
def paused(self):
raise NotImplementedError raise NotImplementedError
class Config(object): class Config(object):
def __init__(self, native=None, port=None, defaults={}): def __init__(self, native=None, port=None, defaults={}):
if not native: if not native:

View File

@ -3,26 +3,27 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 from .core import IRunner, ICoreOwner, Core
import io
import sys import sys
class DebuggerCoreOwner(ICoreOwner): class DebuggerCoreOwner(ICoreOwner):
def __init__(self, debugger): def __init__(self, debugger):
self.debugger = debugger self.debugger = debugger
self.wasPaused = False self.was_paused = False
def claim(self): def claim(self):
if self.debugger.isRunning(): if self.debugger.isRunning():
self.wasPaused = True self.was_paused = True
self.debugger.pause() self.debugger.pause()
return self.debugger._core return self.debugger._core
def release(self): def release(self):
if self.wasPaused: if self.was_paused:
self.debugger.unpause() self.debugger.unpause()
class NativeDebugger(IRunner): class NativeDebugger(IRunner):
WATCHPOINT_WRITE = lib.WATCHPOINT_WRITE WATCHPOINT_WRITE = lib.WATCHPOINT_WRITE
WATCHPOINT_READ = lib.WATCHPOINT_READ WATCHPOINT_READ = lib.WATCHPOINT_READ
@ -49,37 +50,40 @@ class NativeDebugger(IRunner):
def unpause(self): def unpause(self):
self._native.state = lib.DEBUGGER_RUNNING self._native.state = lib.DEBUGGER_RUNNING
def isRunning(self): @property
def running(self):
return self._native.state == lib.DEBUGGER_RUNNING 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) return self._native.state in (lib.DEBUGGER_PAUSED, lib.DEBUGGER_CUSTOM)
def useCore(self): def use_core(self):
return DebuggerCoreOwner(self) return DebuggerCoreOwner(self)
def setBreakpoint(self, address): def set_breakpoint(self, address):
if not self._native.platform.setBreakpoint: if not self._native.platform.setBreakpoint:
raise RuntimeError("Platform does not support breakpoints") raise RuntimeError("Platform does not support breakpoints")
self._native.platform.setBreakpoint(self._native.platform, address) self._native.platform.setBreakpoint(self._native.platform, address)
def clearBreakpoint(self, address): def clear_breakpoint(self, address):
if not self._native.platform.setBreakpoint: if not self._native.platform.setBreakpoint:
raise RuntimeError("Platform does not support breakpoints") raise RuntimeError("Platform does not support breakpoints")
self._native.platform.clearBreakpoint(self._native.platform, address) self._native.platform.clearBreakpoint(self._native.platform, address)
def setWatchpoint(self, address): def set_watchpoint(self, address):
if not self._native.platform.setWatchpoint: if not self._native.platform.setWatchpoint:
raise RuntimeError("Platform does not support watchpoints") raise RuntimeError("Platform does not support watchpoints")
self._native.platform.setWatchpoint(self._native.platform, address) self._native.platform.setWatchpoint(self._native.platform, address)
def clearWatchpoint(self, address): def clear_watchpoint(self, address):
if not self._native.platform.clearWatchpoint: if not self._native.platform.clearWatchpoint:
raise RuntimeError("Platform does not support watchpoints") raise RuntimeError("Platform does not support watchpoints")
self._native.platform.clearWatchpoint(self._native.platform, address) self._native.platform.clearWatchpoint(self._native.platform, address)
def addCallback(self, cb): def add_callback(self, callback):
self._cbs.append(cb) self._cbs.append(callback)
class CLIBackend(object): class CLIBackend(object):
def __init__(self, backend): def __init__(self, backend):
@ -88,6 +92,7 @@ class CLIBackend(object):
def write(self, string): def write(self, string):
self.backend.printf(string) self.backend.printf(string)
class CLIDebugger(NativeDebugger): class CLIDebugger(NativeDebugger):
def __init__(self, native): def __init__(self, native):
super(CLIDebugger, self).__init__(native) super(CLIDebugger, self).__init__(native)
@ -97,5 +102,5 @@ class CLIDebugger(NativeDebugger):
message = message.format(*args, **kwargs) message = message.format(*args, **kwargs)
self._cli.backend.printf(ffi.new("char []", b"%s"), ffi.new("char []", message.encode('utf-8'))) 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) sys.stdout = CLIBackend(self)

View File

@ -8,6 +8,7 @@ try:
except ImportError: except ImportError:
pass pass
def search(core): def search(core):
crc32 = None crc32 = None
if hasattr(core, 'PLATFORM_GBA') and core.platform() == core.PLATFORM_GBA: if hasattr(core, 'PLATFORM_GBA') and core.platform() == core.PLATFORM_GBA:

View File

@ -3,12 +3,13 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 .lr35902 import LR35902Core
from .core import Core, needsReset from .core import Core, needs_reset
from .memory import Memory from .memory import Memory
from .tile import Sprite from .tile import Sprite
from . import createCallback from . import create_callback
class GB(Core): class GB(Core):
KEY_A = lib.GBA_KEY_A KEY_A = lib.GBA_KEY_A
@ -25,31 +26,34 @@ class GB(Core):
self._native = ffi.cast("struct GB*", native.board) self._native = ffi.cast("struct GB*", native.board)
self.sprites = GBObjs(self) self.sprites = GBObjs(self)
self.cpu = LR35902Core(self._core.cpu) self.cpu = LR35902Core(self._core.cpu)
self.memory = None
@needsReset @needs_reset
def _initCache(self, cache): def _init_cache(self, cache):
lib.GBVideoCacheInit(cache) lib.GBVideoCacheInit(cache)
lib.GBVideoCacheAssociate(cache, ffi.addressof(self._native.video)) lib.GBVideoCacheAssociate(cache, ffi.addressof(self._native.video))
def _deinitCache(self, cache): def _deinit_cache(self, cache):
lib.mCacheSetDeinit(cache) lib.mCacheSetDeinit(cache)
if self._wasReset: if self._was_reset:
self._native.video.renderer.cache = ffi.NULL self._native.video.renderer.cache = ffi.NULL
def _load(self): def _load(self):
super(GB, self)._load() super(GB, self)._load()
self.memory = GBMemory(self._core) self.memory = GBMemory(self._core)
def attachSIO(self, link): def attach_sio(self, link):
lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native) lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native)
def __del__(self): def __del__(self):
lib.GBSIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL) lib.GBSIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL)
createCallback("GBSIOPythonDriver", "init")
createCallback("GBSIOPythonDriver", "deinit") create_callback("GBSIOPythonDriver", "init")
createCallback("GBSIOPythonDriver", "writeSB") create_callback("GBSIOPythonDriver", "deinit")
createCallback("GBSIOPythonDriver", "writeSC") create_callback("GBSIOPythonDriver", "writeSB")
create_callback("GBSIOPythonDriver", "writeSC")
class GBSIODriver(object): class GBSIODriver(object):
def __init__(self): def __init__(self):
@ -62,53 +66,55 @@ class GBSIODriver(object):
def deinit(self): def deinit(self):
pass pass
def writeSB(self, value): def write_sb(self, value):
pass pass
def writeSC(self, value): def write_sc(self, value):
return value return value
class GBSIOSimpleDriver(GBSIODriver): class GBSIOSimpleDriver(GBSIODriver):
def __init__(self, period=0x100): def __init__(self, period=0x100):
super(GBSIOSimpleDriver, self).__init__() super(GBSIOSimpleDriver, self).__init__()
self.rx = 0x00 self.rx = 0x00 # pylint: disable=invalid-name
self._period = period self._period = period
def init(self): def init(self):
self._native.p.period = self._period self._native.p.period = self._period
return True return True
def writeSB(self, value): def write_sb(self, value):
self.rx = value self.rx = value # pylint: disable=invalid-name
def writeSC(self, value): def write_sc(self, value):
self._native.p.period = self._period self._native.p.period = self._period
if value & 0x80: if value & 0x80:
lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event)) 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) lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), self._native.p.period)
return value return value
def isReady(self): def is_ready(self):
return not self._native.p.remainingBits return not self._native.p.remainingBits
@property @property
def tx(self): def tx(self): # pylint: disable=invalid-name
self._native.p.pendingSB return self._native.p.pendingSB
@property @property
def period(self): def period(self):
return self._native.p.period return self._native.p.period
@tx.setter @tx.setter
def tx(self, newTx): def tx(self, newTx): # pylint: disable=invalid-name
self._native.p.pendingSB = newTx self._native.p.pendingSB = newTx
self._native.p.remainingBits = 8 self._native.p.remainingBits = 8
@period.setter @period.setter
def period(self, newPeriod): def period(self, new_period):
self._period = newPeriod self._period = new_period
if self._native.p: if self._native.p:
self._native.p.period = newPeriod self._native.p.period = new_period
class GBMemory(Memory): class GBMemory(Memory):
def __init__(self, core): 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.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.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.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) self.hram = Memory(core, lib.GB_SIZE_HRAM, lib.GB_BASE_HRAM)
class GBSprite(Sprite): class GBSprite(Sprite):
PALETTE_BASE = 8, PALETTE_BASE = (8,)
def __init__(self, obj, core): def __init__(self, obj, core):
self.x = obj.x self.x = obj.x # pylint: disable=invalid-name
self.y = obj.y self.y = obj.y # pylint: disable=invalid-name
self.tile = obj.tile self.tile = obj.tile
self._attr = obj.attr self._attr = obj.attr
self.width = 8 self.width = 8
@ -136,10 +143,10 @@ class GBSprite(Sprite):
if core._native.model >= lib.GB_MODEL_CGB: if core._native.model >= lib.GB_MODEL_CGB:
if self._attr & 8: if self._attr & 8:
self.tile += 512 self.tile += 512
self.paletteId = self._attr & 7 self.palette_id = self._attr & 7
else: else:
self.paletteId = (self._attr >> 4) & 1 self.palette_id = (self._attr >> 4) & 1
self.paletteId += 8 self.palette_id += 8
class GBObjs: class GBObjs:

View File

@ -3,12 +3,13 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 .arm import ARMCore
from .core import Core, needsReset from .core import Core, needs_reset
from .tile import Sprite from .tile import Sprite
from .memory import Memory from .memory import Memory
from . import createCallback from . import create_callback
class GBA(Core): class GBA(Core):
KEY_A = lib.GBA_KEY_A KEY_A = lib.GBA_KEY_A
@ -34,23 +35,24 @@ class GBA(Core):
self._native = ffi.cast("struct GBA*", native.board) self._native = ffi.cast("struct GBA*", native.board)
self.sprites = GBAObjs(self) self.sprites = GBAObjs(self)
self.cpu = ARMCore(self._core.cpu) self.cpu = ARMCore(self._core.cpu)
self.memory = None
self._sio = set() self._sio = set()
@needsReset @needs_reset
def _initCache(self, cache): def _init_cache(self, cache):
lib.GBAVideoCacheInit(cache) lib.GBAVideoCacheInit(cache)
lib.GBAVideoCacheAssociate(cache, ffi.addressof(self._native.video)) lib.GBAVideoCacheAssociate(cache, ffi.addressof(self._native.video))
def _deinitCache(self, cache): def _deinit_cache(self, cache):
lib.mCacheSetDeinit(cache) lib.mCacheSetDeinit(cache)
if self._wasReset: if self._was_reset:
self._native.video.renderer.cache = ffi.NULL self._native.video.renderer.cache = ffi.NULL
def _load(self): def _load(self):
super(GBA, self)._load() super(GBA, self)._load()
self.memory = GBAMemory(self._core, self._native.memory.romSize) 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) self._sio.add(mode)
lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode) lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode)
@ -58,14 +60,18 @@ class GBA(Core):
for mode in self._sio: for mode in self._sio:
lib.GBASIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL, mode) lib.GBASIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL, mode)
createCallback("GBASIOPythonDriver", "init")
createCallback("GBASIOPythonDriver", "deinit") create_callback("GBASIOPythonDriver", "init")
createCallback("GBASIOPythonDriver", "load") create_callback("GBASIOPythonDriver", "deinit")
createCallback("GBASIOPythonDriver", "unload") create_callback("GBASIOPythonDriver", "load")
createCallback("GBASIOPythonDriver", "writeRegister") create_callback("GBASIOPythonDriver", "unload")
create_callback("GBASIOPythonDriver", "writeRegister")
class GBASIODriver(object): class GBASIODriver(object):
def __init__(self): def __init__(self):
super(GBASIODriver, self).__init__()
self._handle = ffi.new_handle(self) self._handle = ffi.new_handle(self)
self._native = ffi.gc(lib.GBASIOPythonDriverCreate(self._handle), lib.free) self._native = ffi.gc(lib.GBASIOPythonDriverCreate(self._handle), lib.free)
@ -81,9 +87,10 @@ class GBASIODriver(object):
def unload(self): def unload(self):
return True return True
def writeRegister(self, address, value): def write_register(self, address, value):
return value return value
class GBASIOJOYDriver(GBASIODriver): class GBASIOJOYDriver(GBASIODriver):
RESET = lib.JOY_RESET RESET = lib.JOY_RESET
POLL = lib.JOY_POLL POLL = lib.JOY_POLL
@ -91,10 +98,11 @@ class GBASIOJOYDriver(GBASIODriver):
RECV = lib.JOY_RECV RECV = lib.JOY_RECV
def __init__(self): def __init__(self):
self._handle = ffi.new_handle(self) super(GBASIOJOYDriver, self).__init__()
self._native = ffi.gc(lib.GBASIOJOYPythonDriverCreate(self._handle), lib.free) 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]') buffer = ffi.new('uint8_t[5]')
try: try:
buffer[0] = data[0] buffer[0] = data[0]
@ -110,6 +118,7 @@ class GBASIOJOYDriver(GBASIODriver):
return bytes(buffer[0:outlen]) return bytes(buffer[0:outlen])
return None return None
class GBAMemory(Memory): class GBAMemory(Memory):
def __init__(self, core, romSize=lib.SIZE_CART0): def __init__(self, core, romSize=lib.SIZE_CART0):
super(GBAMemory, self).__init__(core, 0x100000000) super(GBAMemory, self).__init__(core, 0x100000000)
@ -117,7 +126,7 @@ class GBAMemory(Memory):
self.bios = Memory(core, lib.SIZE_BIOS, lib.BASE_BIOS) self.bios = Memory(core, lib.SIZE_BIOS, lib.BASE_BIOS)
self.wram = Memory(core, lib.SIZE_WORKING_RAM, lib.BASE_WORKING_RAM) 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.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.palette = Memory(core, lib.SIZE_PALETTE_RAM, lib.BASE_PALETTE_RAM)
self.vram = Memory(core, lib.SIZE_VRAM, lib.BASE_VRAM) self.vram = Memory(core, lib.SIZE_VRAM, lib.BASE_VRAM)
self.oam = Memory(core, lib.SIZE_OAM, lib.BASE_OAM) self.oam = Memory(core, lib.SIZE_OAM, lib.BASE_OAM)
@ -128,6 +137,7 @@ class GBAMemory(Memory):
self.rom = self.cart0 self.rom = self.cart0
self.sram = Memory(core, lib.SIZE_CART_SRAM, lib.BASE_CART_SRAM) self.sram = Memory(core, lib.SIZE_CART_SRAM, lib.BASE_CART_SRAM)
class GBASprite(Sprite): class GBASprite(Sprite):
TILE_BASE = 0x800, 0x400 TILE_BASE = 0x800, 0x400
PALETTE_BASE = 0x10, 1 PALETTE_BASE = 0x10, 1
@ -136,18 +146,19 @@ class GBASprite(Sprite):
self._a = obj.a self._a = obj.a
self._b = obj.b self._b = obj.b
self._c = obj.c self._c = obj.c
self.x = self._b & 0x1FF self.x = self._b & 0x1FF # pylint: disable=invalid-name
self.y = self._a & 0xFF self.y = self._a & 0xFF # pylint: disable=invalid-name
self._shape = self._a >> 14 self._shape = self._a >> 14
self._size = self._b >> 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.width, self.height = lib.GBAVideoObjSizes[self._shape * 4 + self._size]
self.tile = self._c & 0x3FF self.tile = self._c & 0x3FF
if self._256Color: if self._256_color:
self.paletteId = 0 self.palette_id = 0
self.tile >>= 1 self.tile >>= 1
else: else:
self.paletteId = self._c >> 12 self.palette_id = self._c >> 12
class GBAObjs: class GBAObjs:
def __init__(self, core): def __init__(self, core):
@ -161,7 +172,7 @@ class GBAObjs:
if index >= len(self): if index >= len(self):
raise IndexError() raise IndexError()
sprite = GBASprite(self._obj[index]) sprite = GBASprite(self._obj[index])
tiles = self._core.tiles[3 if sprite._256Color else 2] tiles = self._core.tiles[3 if sprite._256_color else 2]
map1D = bool(self._core._native.memory.io[0] & 0x40) map_1d = bool(self._core._native.memory.io[0] & 0x40)
sprite.constitute(tiles, 0 if map1D else 0x20) sprite.constitute(tiles, 0 if map_1d else 0x20)
return sprite return sprite

View File

@ -3,7 +3,7 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 from . import png
try: try:
@ -11,6 +11,7 @@ try:
except ImportError: except ImportError:
pass pass
class Image: class Image:
def __init__(self, width, height, stride=0, alpha=False): def __init__(self, width, height, stride=0, alpha=False):
self.width = width self.width = width
@ -24,58 +25,63 @@ class Image:
self.stride = self.width self.stride = self.width
self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height)) self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height))
def savePNG(self, f): def save_png(self, fileobj):
p = png.PNG(f, mode=png.MODE_RGBA if self.alpha else png.MODE_RGB) png_file = png.PNG(fileobj, mode=png.MODE_RGBA if self.alpha else png.MODE_RGB)
success = p.writeHeader(self) success = png_file.write_header(self)
success = success and p.writePixels(self) success = success and png_file.write_pixels(self)
p.writeClose() png_file.write_close()
return success return success
if 'PImage' in globals(): if 'PImage' in globals():
def toPIL(self): def to_pil(self):
type = "RGBA" if self.alpha else "RGBX" colorspace = "RGBA" if self.alpha else "RGBX"
return PImage.frombytes(type, (self.width, self.height), ffi.buffer(self.buffer), "raw", return PImage.frombytes(colorspace, (self.width, self.height), ffi.buffer(self.buffer), "raw",
type, self.stride * 4) colorspace, self.stride * 4)
def u16ToU32(c):
r = c & 0x1F def u16_to_u32(color):
g = (c >> 5) & 0x1F # pylint: disable=invalid-name
b = (c >> 10) & 0x1F r = color & 0x1F
a = (c >> 15) & 1 g = (color >> 5) & 0x1F
b = (color >> 10) & 0x1F
a = (color >> 15) & 1
abgr = r << 3 abgr = r << 3
abgr |= g << 11 abgr |= g << 11
abgr |= b << 19 abgr |= b << 19
abgr |= (a * 0xFF) << 24 abgr |= (a * 0xFF) << 24
return abgr return abgr
def u32ToU16(c):
r = (c >> 3) & 0x1F def u32_to_u16(color):
g = (c >> 11) & 0x1F # pylint: disable=invalid-name
b = (c >> 19) & 0x1F r = (color >> 3) & 0x1F
a = c >> 31 g = (color >> 11) & 0x1F
b = (color >> 19) & 0x1F
a = color >> 31
abgr = r abgr = r
abgr |= g << 5 abgr |= g << 5
abgr |= b << 10 abgr |= b << 10
abgr |= a << 15 abgr |= a << 15
return abgr return abgr
if ffi.sizeof("color_t") == 2: if ffi.sizeof("color_t") == 2:
def colorToU16(c): def color_to_u16(color):
return c return color
colorToU32 = u16ToU32 color_to_u32 = u16_to_u32 # pylint: disable=invalid-name
def u16ToColor(c): def u16_to_color(color):
return c return color
u32ToColor = u32ToU16 u32_to_color = u32_to_u16 # pylint: disable=invalid-name
else: else:
def colorToU32(c): def color_to_u32(color):
return c return color
colorToU16 = u32ToU16 color_to_u16 = u32_to_u16 # pylint: disable=invalid-name
def u32ToColor(c): def u32_to_color(color):
return c return color
u16ToColor = u16ToU32 u16_to_color = u16_to_u32 # pylint: disable=invalid-name

View File

@ -3,17 +3,15 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 createCallback from . import create_callback
createCallback("mLoggerPy", "log", "_pyLog") create_callback("mLoggerPy", "log", "_pyLog")
defaultLogger = None
def installDefault(logger): def install_default(logger):
global defaultLogger Logger.install_default(logger)
defaultLogger = logger
lib.mLogSetDefaultLogger(logger._native)
class Logger(object): class Logger(object):
FATAL = lib.mLOG_FATAL FATAL = lib.mLOG_FATAL
@ -24,16 +22,24 @@ class Logger(object):
STUB = lib.mLOG_STUB STUB = lib.mLOG_STUB
GAME_ERROR = lib.mLOG_GAME_ERROR GAME_ERROR = lib.mLOG_GAME_ERROR
_DEFAULT_LOGGER = None
def __init__(self): def __init__(self):
self._handle = ffi.new_handle(self) self._handle = ffi.new_handle(self)
self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free)
@staticmethod @staticmethod
def categoryName(category): def category_name(category):
return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') 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): def log(self, category, level, message):
print("{}: {}".format(self.categoryName(category), message)) print("{}: {}".format(self.category_name(category), message))
class NullLogger(Logger): class NullLogger(Logger):
def log(self, category, level, message): def log(self, category, level, message):

View File

@ -3,9 +3,11 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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: class LR35902Core:
# pylint: disable=invalid-name
def __init__(self, native): def __init__(self, native):
self._native = ffi.cast("struct LR35902Core*", native) self._native = ffi.cast("struct LR35902Core*", native)

View File

@ -3,7 +3,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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): class MemoryView(object):
def __init__(self, core, width, size, base=0, sign="u"): def __init__(self, core, width, size, base=0, sign="u"):
@ -11,11 +12,11 @@ class MemoryView(object):
self._width = width self._width = width
self._size = size self._size = size
self._base = base self._base = base
self._busRead = getattr(self._core, "busRead" + str(width * 8)) self._bus_read = getattr(self._core, "busRead" + str(width * 8))
self._busWrite = getattr(self._core, "busWrite" + str(width * 8)) self._bus_write = getattr(self._core, "busWrite" + str(width * 8))
self._rawRead = getattr(self._core, "rawRead" + str(width * 8)) self._raw_read = getattr(self._core, "rawRead" + str(width * 8))
self._rawWrite = getattr(self._core, "rawWrite" + 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 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": if sign == "u" or sign == "unsigned":
self._type = "uint{}_t".format(width * 8) self._type = "uint{}_t".format(width * 8)
elif sign == "i" or sign == "s" or sign == "signed": elif sign == "i" or sign == "s" or sign == "signed":
@ -23,7 +24,7 @@ class MemoryView(object):
else: else:
raise ValueError("Invalid sign type: '{}'".format(sign)) raise ValueError("Invalid sign type: '{}'".format(sign))
def _addrCheck(self, address): def _addr_check(self, address):
if isinstance(address, slice): if isinstance(address, slice):
start = address.start or 0 start = address.start or 0
stop = self._size - self._width if address.stop is None else address.stop stop = self._size - self._width if address.stop is None else address.stop
@ -39,33 +40,32 @@ class MemoryView(object):
return self._size return self._size
def __getitem__(self, address): def __getitem__(self, address):
self._addrCheck(address) self._addr_check(address)
if isinstance(address, slice): if isinstance(address, slice):
start = address.start or 0 start = address.start or 0
stop = self._size - self._width if address.stop is None else address.stop stop = self._size - self._width if address.stop is None else address.stop
step = address.step or self._width 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)] return [int(ffi.cast(self._type, self._bus_read(self._core, self._base + a))) for a in range(start, stop, step)]
else: return int(ffi.cast(self._type, self._bus_read(self._core, self._base + address)))
return int(ffi.cast(self._type, self._busRead(self._core, self._base + address)))
def __setitem__(self, address, value): def __setitem__(self, address, value):
self._addrCheck(address) self._addr_check(address)
if isinstance(address, slice): if isinstance(address, slice):
start = address.start or 0 start = address.start or 0
stop = self._size - self._width if address.stop is None else address.stop stop = self._size - self._width if address.stop is None else address.stop
step = address.step or self._width step = address.step or self._width
for a in range(start, stop, step): for addr in range(start, stop, step):
self._busWrite(self._core, self._base + a, value[a] & self._mask) self._bus_write(self._core, self._base + addr, value[addr] & self._mask)
else: 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): def raw_read(self, address, segment=-1):
self._addrCheck(address) self._addr_check(address)
return int(ffi.cast(self._type, self._rawRead(self._core, self._base + address, segment))) return int(ffi.cast(self._type, self._raw_read(self._core, self._base + address, segment)))
def rawWrite(self, address, value, segment=-1): def raw_write(self, address, value, segment=-1):
self._addrCheck(address) self._addr_check(address)
self._rawWrite(self._core, self._base + address, segment, value & self._mask) self._raw_write(self._core, self._base + address, segment, value & self._mask)
class MemorySearchResult(object): class MemorySearchResult(object):
@ -75,12 +75,13 @@ class MemorySearchResult(object):
self.guessDivisor = result.guessDivisor self.guessDivisor = result.guessDivisor
self.type = result.type self.type = result.type
if result.type == Memory.SEARCH_8: if result.type == Memory.SEARCH_INT:
self._memory = memory.u8 if result.width == 1:
elif result.type == Memory.SEARCH_16: self._memory = memory.u8
self._memory = memory.u16 elif result.width == 2:
elif result.type == Memory.SEARCH_32: self._memory = memory.u16
self._memory = memory.u32 elif result.width == 4:
self._memory = memory.u32
elif result.type == Memory.SEARCH_STRING: elif result.type == Memory.SEARCH_STRING:
self._memory = memory.u8 self._memory = memory.u8
else: else:
@ -123,7 +124,7 @@ class Memory(object):
self.s32 = MemoryView(core, 4, size, base, "s") self.s32 = MemoryView(core, 4, size, base, "s")
def __len__(self): def __len__(self):
return self._size return self.size
def search(self, value, type=SEARCH_GUESS, flags=RW, limit=10000, old_results=[]): def search(self, value, type=SEARCH_GUESS, flags=RW, limit=10000, old_results=[]):
results = ffi.new("struct mCoreMemorySearchResults*") results = ffi.new("struct mCoreMemorySearchResults*")
@ -138,11 +139,11 @@ class Memory(object):
params.valueStr = ffi.new("char[]", str(value).encode("ascii")) params.valueStr = ffi.new("char[]", str(value).encode("ascii"))
for result in old_results: for result in old_results:
r = lib.mCoreMemorySearchResultsAppend(results) native_result = lib.mCoreMemorySearchResultsAppend(results)
r.address = result.address native_result.address = result.address
r.segment = result.segment native_result.segment = result.segment
r.guessDivisor = result.guessDivisor native_result.guessDivisor = result.guessDivisor
r.type = result.type native_result.type = result.type
if old_results: if old_results:
lib.mCoreMemorySearchRepeat(self._core, params, results) lib.mCoreMemorySearchRepeat(self._core, params, results)
else: else:
@ -154,5 +155,4 @@ class Memory(object):
def __getitem__(self, address): def __getitem__(self, address):
if isinstance(address, slice): if isinstance(address, slice):
return bytearray(self.u8[address]) return bytearray(self.u8[address])
else: return self.u8[address]
return self.u8[address]

View File

@ -3,37 +3,41 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 from . import vfs
MODE_RGB = 0 MODE_RGB = 0
MODE_RGBA = 1 MODE_RGBA = 1
MODE_INDEX = 2 MODE_INDEX = 2
class PNG: class PNG:
def __init__(self, f, mode=MODE_RGB): def __init__(self, f, mode=MODE_RGB):
self.vf = vfs.open(f) self._vfile = vfs.open(f)
self.mode = mode self._png = None
self._info = None
self.mode = mode
def writeHeader(self, image): def write_header(self, image):
self._png = lib.PNGWriteOpen(self.vf.handle) self._png = lib.PNGWriteOpen(self._vfile.handle)
if self.mode == MODE_RGB: if self.mode == MODE_RGB:
self._info = lib.PNGWriteHeader(self._png, image.width, image.height) self._info = lib.PNGWriteHeader(self._png, image.width, image.height)
if self.mode == MODE_RGBA: if self.mode == MODE_RGBA:
self._info = lib.PNGWriteHeaderA(self._png, image.width, image.height) self._info = lib.PNGWriteHeaderA(self._png, image.width, image.height)
if self.mode == MODE_INDEX: if self.mode == MODE_INDEX:
self._info = lib.PNGWriteHeader8(self._png, image.width, image.height) self._info = lib.PNGWriteHeader8(self._png, image.width, image.height)
return self._info != ffi.NULL return self._info != ffi.NULL
def writePixels(self, image): def write_pixels(self, image):
if self.mode == MODE_RGB: if self.mode == MODE_RGB:
return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer) return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer)
if self.mode == MODE_RGBA: if self.mode == MODE_RGBA:
return lib.PNGWritePixelsA(self._png, image.width, image.height, image.stride, image.buffer) return lib.PNGWritePixelsA(self._png, image.width, image.height, image.stride, image.buffer)
if self.mode == MODE_INDEX: if self.mode == MODE_INDEX:
return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer) 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) lib.PNGWriteClose(self._png, self._info)
del self._png self._png = None
del self._info self._info = None

View File

@ -3,9 +3,10 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 from .core import IRunner, ICoreOwner, Core
class ThreadCoreOwner(ICoreOwner): class ThreadCoreOwner(ICoreOwner):
def __init__(self, thread): def __init__(self, thread):
self.thread = thread self.thread = thread
@ -19,12 +20,13 @@ class ThreadCoreOwner(ICoreOwner):
def release(self): def release(self):
lib.mCoreThreadContinue(self.thread._native) lib.mCoreThreadContinue(self.thread._native)
class Thread(IRunner): class Thread(IRunner):
def __init__(self, native=None): def __init__(self, native=None):
if native: if native:
self._native = native self._native = native
self._core = Core(native.core) self._core = Core(native.core)
self._core._wasReset = lib.mCoreThreadHasStarted(self._native) self._core._was_reset = lib.mCoreThreadHasStarted(self._native)
else: else:
self._native = ffi.new("struct mCoreThread*") self._native = ffi.new("struct mCoreThread*")
@ -34,7 +36,7 @@ class Thread(IRunner):
self._core = core self._core = core
self._native.core = core._core self._native.core = core._core
lib.mCoreThreadStart(self._native) lib.mCoreThreadStart(self._native)
self._core._wasReset = lib.mCoreThreadHasStarted(self._native) self._core._was_reset = lib.mCoreThreadHasStarted(self._native)
def end(self): def end(self):
if not lib.mCoreThreadHasStarted(self._native): if not lib.mCoreThreadHasStarted(self._native):
@ -48,11 +50,13 @@ class Thread(IRunner):
def unpause(self): def unpause(self):
lib.mCoreThreadUnpause(self._native) lib.mCoreThreadUnpause(self._native)
def isRunning(self): @property
def running(self):
return bool(lib.mCoreThreadIsActive(self._native)) return bool(lib.mCoreThreadIsActive(self._native))
def isPaused(self): @property
def paused(self):
return bool(lib.mCoreThreadIsPaused(self._native)) return bool(lib.mCoreThreadIsPaused(self._native))
def useCore(self): def use_core(self):
return ThreadCoreOwner(self) return ThreadCoreOwner(self)

View File

@ -3,14 +3,15 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # 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 from . import image
class Tile: class Tile:
def __init__(self, data): def __init__(self, data):
self.buffer = data self.buffer = data
def toImage(self): def to_image(self):
i = image.Image(8, 8) i = image.Image(8, 8)
self.composite(i, 0, 0) self.composite(i, 0, 0)
return i return i
@ -19,19 +20,22 @@ class Tile:
for iy in range(8): 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")) ffi.memmove(ffi.addressof(i.buffer, x + (iy + y) * i.stride), ffi.addressof(self.buffer, iy * 8), 8 * ffi.sizeof("color_t"))
class CacheSet: class CacheSet:
def __init__(self, core): def __init__(self, core):
self.core = core self.core = core
self.cache = ffi.gc(ffi.new("struct mCacheSet*"), core._deinitCache) self.cache = ffi.gc(ffi.new("struct mCacheSet*"), core._deinit_cache)
core._initCache(self.cache) core._init_cache(self.cache)
class TileView: class TileView:
def __init__(self, cache): def __init__(self, cache):
self.cache = cache self.cache = cache
def getTile(self, tile, palette): def get_tile(self, tile, palette):
return Tile(lib.mTileCacheGetTile(self.cache, tile, palette)) return Tile(lib.mTileCacheGetTile(self.cache, tile, palette))
class MapView: class MapView:
def __init__(self, cache): def __init__(self, cache):
self.cache = 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")) ffi.memmove(ffi.addressof(i.buffer, i.stride * y), row, self.width * 8 * ffi.sizeof("color_t"))
return i return i
class Sprite(object): class Sprite(object):
def constitute(self, tileView, tilePitch): def constitute(self, tileView, tilePitch):
i = image.Image(self.width, self.height, alpha=True) i = image.Image(self.width, self.height, alpha=True)

View File

@ -3,139 +3,152 @@
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # 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/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
from ._pylib import ffi, lib # pylint: disable=invalid-name,unused-argument
import mmap from ._pylib import ffi, lib # pylint: disable=no-name-in-module
import os import os
@ffi.def_extern() @ffi.def_extern()
def _vfpClose(vf): def _vfpClose(vf):
vfp = ffi.cast("struct VFilePy*", vf) vfp = ffi.cast("struct VFilePy*", vf)
ffi.from_handle(vfp.fileobj).close() ffi.from_handle(vfp.fileobj).close()
return True return True
@ffi.def_extern() @ffi.def_extern()
def _vfpSeek(vf, offset, whence): def _vfpSeek(vf, offset, whence):
vfp = ffi.cast("struct VFilePy*", vf) vfp = ffi.cast("struct VFilePy*", vf)
f = ffi.from_handle(vfp.fileobj) f = ffi.from_handle(vfp.fileobj)
f.seek(offset, whence) f.seek(offset, whence)
return f.tell() return f.tell()
@ffi.def_extern() @ffi.def_extern()
def _vfpRead(vf, buffer, size): def _vfpRead(vf, buffer, size):
vfp = ffi.cast("struct VFilePy*", vf) vfp = ffi.cast("struct VFilePy*", vf)
pybuf = ffi.buffer(buffer, size) pybuf = ffi.buffer(buffer, size)
ffi.from_handle(vfp.fileobj).readinto(pybuf) ffi.from_handle(vfp.fileobj).readinto(pybuf)
return size return size
@ffi.def_extern() @ffi.def_extern()
def _vfpWrite(vf, buffer, size): def _vfpWrite(vf, buffer, size):
vfp = ffi.cast("struct VFilePy*", vf) vfp = ffi.cast("struct VFilePy*", vf)
pybuf = ffi.buffer(buffer, size) pybuf = ffi.buffer(buffer, size)
ffi.from_handle(vfp.fileobj).write(pybuf) ffi.from_handle(vfp.fileobj).write(pybuf)
return size return size
@ffi.def_extern() @ffi.def_extern()
def _vfpMap(vf, size, flags): def _vfpMap(vf, size, flags):
pass pass
@ffi.def_extern() @ffi.def_extern()
def _vfpUnmap(vf, memory, size): def _vfpUnmap(vf, memory, size):
pass pass
@ffi.def_extern() @ffi.def_extern()
def _vfpTruncate(vf, size): def _vfpTruncate(vf, size):
vfp = ffi.cast("struct VFilePy*", vf) vfp = ffi.cast("struct VFilePy*", vf)
ffi.from_handle(vfp.fileobj).truncate(size) ffi.from_handle(vfp.fileobj).truncate(size)
@ffi.def_extern() @ffi.def_extern()
def _vfpSize(vf): def _vfpSize(vf):
vfp = ffi.cast("struct VFilePy*", vf) vfp = ffi.cast("struct VFilePy*", vf)
f = ffi.from_handle(vfp.fileobj) f = ffi.from_handle(vfp.fileobj)
pos = f.tell() pos = f.tell()
f.seek(0, os.SEEK_END) f.seek(0, os.SEEK_END)
size = f.tell() size = f.tell()
f.seek(pos, os.SEEK_SET) f.seek(pos, os.SEEK_SET)
return size return size
@ffi.def_extern() @ffi.def_extern()
def _vfpSync(vf, buffer, size): def _vfpSync(vf, buffer, size):
vfp = ffi.cast("struct VFilePy*", vf) vfp = ffi.cast("struct VFilePy*", vf)
f = ffi.from_handle(vfp.fileobj) f = ffi.from_handle(vfp.fileobj)
if buffer and size: if buffer and size:
pos = f.tell() pos = f.tell()
f.seek(0, os.SEEK_SET) f.seek(0, os.SEEK_SET)
_vfpWrite(vf, buffer, size) _vfpWrite(vf, buffer, size)
f.seek(pos, os.SEEK_SET) f.seek(pos, os.SEEK_SET)
f.flush() f.flush()
os.fsync() os.fsync()
return True 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"): def open(f): # pylint: disable=redefined-builtin
flags = 0 handle = ffi.new_handle(f)
if mode.startswith("r"): vf = VFile(lib.VFileFromPython(handle), _no_gc=(f, handle))
flags |= os.O_RDONLY return vf
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
vf = lib.VFileOpen(path.encode("UTF-8"), flags); def open_path(path, mode="r"):
if vf == ffi.NULL: flags = 0
return None if mode.startswith("r"):
return VFile(vf) 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
vf = lib.VFileOpen(path.encode("UTF-8"), flags)
if vf == ffi.NULL:
return None
return VFile(vf)
class VFile: class VFile:
def __init__(self, vf): def __init__(self, vf, _no_gc=None):
self.handle = vf self.handle = vf
self._no_gc = _no_gc
def close(self): def __del__(self):
return bool(self.handle.close(self.handle)) self.close()
def seek(self, offset, whence): def close(self):
return self.handle.seek(self.handle, offset, whence) return bool(self.handle.close(self.handle))
def read(self, buffer, size): def seek(self, offset, whence):
return self.handle.read(self.handle, buffer, size) return self.handle.seek(self.handle, offset, whence)
def readAll(self, size=0): def read(self, buffer, size):
if not size: return self.handle.read(self.handle, buffer, size)
size = self.size()
buffer = ffi.new("char[%i]" % size)
size = self.handle.read(self.handle, buffer, size)
return ffi.unpack(buffer, size)
def readline(self, buffer, size): def read_all(self, size=0):
return self.handle.readline(self.handle, buffer, size) if not size:
size = self.size()
buffer = ffi.new("char[%i]" % size)
size = self.handle.read(self.handle, buffer, size)
return ffi.unpack(buffer, size)
def write(self, buffer, size): def readline(self, buffer, size):
return self.handle.write(self.handle, buffer, size) return self.handle.readline(self.handle, buffer, size)
def map(self, size, flags): def write(self, buffer, size):
return self.handle.map(self.handle, size, flags) return self.handle.write(self.handle, buffer, size)
def unmap(self, memory, size): def map(self, size, flags):
self.handle.unmap(self.handle, memory, size) return self.handle.map(self.handle, size, flags)
def truncate(self, size): def unmap(self, memory, size):
self.handle.truncate(self.handle, size) self.handle.unmap(self.handle, memory, size)
def size(self): def truncate(self, size):
return self.handle.size(self.handle) self.handle.truncate(self.handle, size)
def sync(self, buffer, size): def size(self):
return self.handle.sync(self.handle, buffer, size) return self.handle.size(self.handle)
def sync(self, buffer, size):
return self.handle.sync(self.handle, buffer, size)

View File

@ -1,2 +1,6 @@
[aliases] [aliases]
test=pytest test=pytest
[pycodestyle]
exclude = .eggs
ignore = E501,E741,E743

View File

@ -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"
]
)

View File

@ -4,7 +4,7 @@ import mgba.log
import os.path import os.path
import yaml import yaml
mgba.log.installDefault(mgba.log.NullLogger()) mgba.log.install_default(mgba.log.NullLogger())
def flatten(d): def flatten(d):
l = [] l = []
@ -18,7 +18,7 @@ def flatten(d):
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
if 'vtest' in metafunc.fixturenames: 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) testList = flatten(tests)
params = [] params = []
for test in testList: for test in testList:
@ -34,9 +34,9 @@ def vtest(request):
return request.param return request.param
def test_video(vtest, pytestconfig): def test_video(vtest, pytestconfig):
vtest.setUp() vtest.setup()
if pytestconfig.getoption('--rebaseline'): if pytestconfig.getoption('--rebaseline'):
vtest.generateBaseline() vtest.generate_baseline()
else: else:
try: try:
vtest.test() vtest.test()

View File

@ -4,26 +4,30 @@ import os
import mgba.vfs as vfs import mgba.vfs as vfs
from mgba._pylib import ffi from mgba._pylib import ffi
def test_vfs_open(): def test_vfs_open():
with open(__file__) as f: with open(__file__) as f:
vf = vfs.open(f) vf = vfs.open(f)
assert vf assert vf
assert vf.close() 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
assert vf.close() assert vf.close()
def test_vfs_read(): def test_vfs_read():
vf = vfs.openPath(__file__) vf = vfs.open_path(__file__)
buffer = ffi.new('char[13]') buffer = ffi.new('char[13]')
assert vf.read(buffer, 13) == 13 assert vf.read(buffer, 13) == 13
assert ffi.string(buffer) == b'import pytest' assert ffi.string(buffer) == b'import pytest'
vf.close() vf.close()
def test_vfs_readline(): def test_vfs_readline():
vf = vfs.openPath(__file__) vf = vfs.open_path(__file__)
buffer = ffi.new('char[16]') buffer = ffi.new('char[16]')
linelen = vf.readline(buffer, 16) linelen = vf.readline(buffer, 16)
assert linelen in (14, 15) assert linelen in (14, 15)
@ -33,16 +37,18 @@ def test_vfs_readline():
assert ffi.string(buffer) == b'import pytest\r\n' assert ffi.string(buffer) == b'import pytest\r\n'
vf.close() vf.close()
def test_vfs_readAllSize():
vf = vfs.openPath(__file__) def test_vfs_read_all_size():
buffer = vf.readAll() vf = vfs.open_path(__file__)
buffer = vf.read_all()
assert buffer assert buffer
assert len(buffer) assert len(buffer)
assert len(buffer) == vf.size() assert len(buffer) == vf.size()
vf.close() vf.close()
def test_vfs_seek(): def test_vfs_seek():
vf = vfs.openPath(__file__) vf = vfs.open_path(__file__)
assert vf.seek(0, os.SEEK_SET) == 0 assert vf.seek(0, os.SEEK_SET) == 0
assert vf.seek(1, os.SEEK_SET) == 1 assert vf.seek(1, os.SEEK_SET) == 1
assert vf.seek(1, os.SEEK_CUR) == 2 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 assert vf.seek(-1, os.SEEK_END) == vf.size() -1
vf.close() 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 assert not vf

View File

@ -6,9 +6,7 @@ set(PLATFORM_SRC)
set(QT_STATIC OFF) set(QT_STATIC OFF)
if(BUILD_SDL) if(BUILD_SDL)
if(NOT SDL_FOUND AND NOT SDL2_FOUND) add_definitions(-DBUILD_SDL)
find_package(SDL 1.2 REQUIRED)
endif()
if(SDL2_FOUND) if(SDL2_FOUND)
link_directories(${SDL2_LIBDIR}) link_directories(${SDL2_LIBDIR})
endif() endif()
@ -33,9 +31,9 @@ if(NOT BUILD_GL AND NOT BUILD_GLES2)
message(WARNING "OpenGL is recommended to build the Qt port") message(WARNING "OpenGL is recommended to build the Qt port")
endif() endif()
set(FOUND_QT ${Qt5Widgets_FOUND} PARENT_SCOPE)
if(NOT Qt5Widgets_FOUND) if(NOT Qt5Widgets_FOUND)
message(WARNING "Cannot find Qt modules") message(WARNING "Cannot find Qt modules")
set(BUILD_QT OFF PARENT_SCOPE)
return() return()
endif() endif()

View File

@ -204,13 +204,13 @@ CoreController::~CoreController() {
stop(); stop();
disconnect(); disconnect();
mCoreThreadJoin(&m_threadContext);
if (m_cacheSet) { if (m_cacheSet) {
mCacheSetDeinit(m_cacheSet.get()); mCacheSetDeinit(m_cacheSet.get());
m_cacheSet.reset(); m_cacheSet.reset();
} }
mCoreThreadJoin(&m_threadContext);
mCoreConfigDeinit(&m_threadContext.core->config); mCoreConfigDeinit(&m_threadContext.core->config);
m_threadContext.core->deinit(m_threadContext.core); m_threadContext.core->deinit(m_threadContext.core);
} }
@ -360,6 +360,7 @@ void CoreController::start() {
} }
void CoreController::stop() { void CoreController::stop() {
setSync(false);
#ifdef USE_DEBUGGERS #ifdef USE_DEBUGGERS
setDebugger(nullptr); setDebugger(nullptr);
#endif #endif

View File

@ -10,6 +10,7 @@
#include "GamepadButtonEvent.h" #include "GamepadButtonEvent.h"
#include "VFileDevice.h" #include "VFileDevice.h"
#include <QAction>
#include <QDateTime> #include <QDateTime>
#include <QKeyEvent> #include <QKeyEvent>
#include <QPainter> #include <QPainter>

View File

@ -160,10 +160,12 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi
connect(this, &Window::shutdown, m_logView, &QWidget::hide); connect(this, &Window::shutdown, m_logView, &QWidget::hide);
connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS); connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS);
connect(&m_frameTimer, &QTimer::timeout, this, &Window::delimitFrames);
connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck); connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck);
m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL); m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL);
m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);
m_frameTimer.setInterval(FRAME_LIST_INTERVAL);
m_focusCheck.setInterval(200); m_focusCheck.setInterval(200);
setupMenu(menuBar()); setupMenu(menuBar());
@ -314,6 +316,7 @@ QString Window::getFilters() const {
QStringList gbFormats{ QStringList gbFormats{
"*.gb", "*.gb",
"*.gbc", "*.gbc",
"*.sgb",
#if defined(USE_LIBZIP) || defined(USE_ZLIB) #if defined(USE_LIBZIP) || defined(USE_ZLIB)
"*.zip", "*.zip",
#endif #endif
@ -726,8 +729,6 @@ void Window::gameStarted() {
return; return;
} }
#endif #endif
multiplayerChanged();
updateTitle();
QSize size = m_controller->screenDimensions(); QSize size = m_controller->screenDimensions();
m_screenWidget->setDimensions(size.width(), size.height()); m_screenWidget->setDimensions(size.width(), size.height());
m_config->updateOption("lockIntegerScaling"); m_config->updateOption("lockIntegerScaling");
@ -744,10 +745,16 @@ void Window::gameStarted() {
menuBar()->hide(); menuBar()->hide();
} }
#endif #endif
m_display->startDrawing(m_controller);
reloadAudioDriver();
multiplayerChanged();
updateTitle();
m_hitUnimplementedBiosCall = false; m_hitUnimplementedBiosCall = false;
if (m_config->getOption("showFps", "1").toInt()) { if (m_config->getOption("showFps", "1").toInt()) {
m_fpsTimer.start(); m_fpsTimer.start();
m_frameTimer.start();
} }
m_focusCheck.start(); m_focusCheck.start();
if (m_display->underMouse()) { if (m_display->underMouse()) {
@ -785,12 +792,10 @@ void Window::gameStarted() {
m_audioChannels->addAction(action); m_audioChannels->addAction(action);
} }
} }
m_display->startDrawing(m_controller);
reloadAudioDriver();
} }
void Window::gameStopped() { void Window::gameStopped() {
m_controller.reset();
for (QPair<QAction*, int> action : m_platformActions) { for (QPair<QAction*, int> action : m_platformActions) {
action.first->setDisabled(false); action.first->setDisabled(false);
} }
@ -816,6 +821,7 @@ void Window::gameStopped() {
m_audioChannels->clear(); m_audioChannels->clear();
m_fpsTimer.stop(); m_fpsTimer.stop();
m_frameTimer.stop();
m_focusCheck.stop(); m_focusCheck.stop();
emit paused(false); emit paused(false);
@ -944,19 +950,27 @@ void Window::mustRestart() {
} }
void Window::recordFrame() { void Window::recordFrame() {
m_frameList.append(QDateTime::currentDateTime()); if (m_frameList.isEmpty()) {
while (m_frameList.count() > FRAME_LIST_SIZE) { m_frameList.append(1);
m_frameList.removeFirst(); } else {
++m_frameList.back();
} }
} }
void Window::delimitFrames() {
if (m_frameList.size() >= FRAME_LIST_SIZE) {
m_frameCounter -= m_frameList.takeAt(0);
}
m_frameCounter += m_frameList.back();
m_frameList.append(0);
}
void Window::showFPS() { void Window::showFPS() {
if (m_frameList.isEmpty()) { if (m_frameList.isEmpty()) {
updateTitle(); updateTitle();
return; return;
} }
qint64 interval = m_frameList.first().msecsTo(m_frameList.last()); float fps = m_frameCounter * 10000.f / (FRAME_LIST_INTERVAL * (m_frameList.size() - 1));
float fps = (m_frameList.count() - 1) * 10000.f / interval;
fps = round(fps) / 10.f; fps = round(fps) / 10.f;
updateTitle(fps); updateTitle(fps);
} }
@ -1679,9 +1693,11 @@ void Window::setupMenu(QMenuBar* menubar) {
showFps->connect([this](const QVariant& value) { showFps->connect([this](const QVariant& value) {
if (!value.toInt()) { if (!value.toInt()) {
m_fpsTimer.stop(); m_fpsTimer.stop();
m_frameTimer.stop();
updateTitle(); updateTitle();
} else if (m_controller) { } else if (m_controller) {
m_fpsTimer.start(); m_fpsTimer.start();
m_frameTimer.start();
} }
}, this); }, this);
@ -1792,17 +1808,19 @@ void Window::setController(CoreController* controller, const QString& fname) {
if (!controller) { if (!controller) {
return; return;
} }
if (m_controller) {
m_controller->stop();
QTimer::singleShot(0, this, [this, controller, fname]() {
setController(controller, fname);
});
return;
}
if (!fname.isEmpty()) { if (!fname.isEmpty()) {
setWindowFilePath(fname); setWindowFilePath(fname);
appendMRU(fname); appendMRU(fname);
} }
if (m_controller) {
m_controller->disconnect(this);
m_controller->stop();
m_controller.reset();
}
m_controller = std::shared_ptr<CoreController>(controller); m_controller = std::shared_ptr<CoreController>(controller);
m_inputController.recalibrateAxes(); m_inputController.recalibrateAxes();
m_controller->setInputController(&m_inputController); m_controller->setInputController(&m_inputController);

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once #pragma once
#include <QAction>
#include <QDateTime> #include <QDateTime>
#include <QList> #include <QList>
#include <QMainWindow> #include <QMainWindow>
@ -130,6 +131,7 @@ private slots:
void mustRestart(); void mustRestart();
void recordFrame(); void recordFrame();
void delimitFrames();
void showFPS(); void showFPS();
void focusCheck(); void focusCheck();
@ -137,7 +139,8 @@ private slots:
private: private:
static const int FPS_TIMER_INTERVAL = 2000; static const int FPS_TIMER_INTERVAL = 2000;
static const int FRAME_LIST_SIZE = 120; static const int FRAME_LIST_INTERVAL = 100;
static const int FRAME_LIST_SIZE = 40;
void setupMenu(QMenuBar*); void setupMenu(QMenuBar*);
void openStateWindow(LoadSave); void openStateWindow(LoadSave);
@ -183,8 +186,10 @@ private:
QPixmap m_logo{":/res/medusa-bg.jpg"}; QPixmap m_logo{":/res/medusa-bg.jpg"};
ConfigController* m_config; ConfigController* m_config;
InputController m_inputController; InputController m_inputController;
QList<QDateTime> m_frameList; QList<int> m_frameList;
int m_frameCounter = 0;
QTimer m_fpsTimer; QTimer m_fpsTimer;
QTimer m_frameTimer;
QList<QString> m_mruFiles; QList<QString> m_mruFiles;
QMenu* m_mruMenu = nullptr; QMenu* m_mruMenu = nullptr;
QMenu* m_videoLayers; QMenu* m_videoLayers;

View File

@ -37,8 +37,10 @@ Q_IMPORT_PLUGIN(AVFServicePlugin);
using namespace QGBA; using namespace QGBA;
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0) #ifdef BUILD_SDL
#if SDL_VERSION_ATLEAST(2, 0, 0) // CPP does not shortcut function lookup
SDL_SetMainReady(); SDL_SetMainReady();
#endif
#endif #endif
ConfigController configController; ConfigController configController;

View File

@ -13,13 +13,15 @@ endif()
if(SDL_VERSION EQUAL "1.2" OR NOT SDL2_FOUND) if(SDL_VERSION EQUAL "1.2" OR NOT SDL2_FOUND)
find_package(SDL 1.2) find_package(SDL 1.2)
set(SDL_VERSION "1.2" PARENT_SCOPE) if(SDL_FOUND)
set(SDL_VERSION_DEBIAN "1.2debian") set(SDL_VERSION "1.2" PARENT_SCOPE)
set(USE_PIXMAN ON) set(SDL_VERSION_DEBIAN "1.2debian")
set(USE_PIXMAN ON)
endif()
endif() endif()
if (NOT SDL2_FOUND AND NOT SDL_FOUND) if (NOT SDL2_FOUND AND NOT SDL_FOUND)
set(BUILD_SDL OFF PARENT_SCOPE) set(SDL_FOUND OFF PARENT_SCOPE)
return() return()
endif() endif()

View File

@ -47,7 +47,7 @@ if(NOT GIT_BRANCH)
endif() endif()
if(DEFINED PRINT_STRING) 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) elseif(NOT VERSION_STRING_CACHE OR NOT VERSION_STRING STREQUAL VERSION_STRING_CACHE)
set(VERSION_STRING_CACHE ${VERSION_STRING} CACHE STRING "" FORCE) set(VERSION_STRING_CACHE ${VERSION_STRING} CACHE STRING "" FORCE)