Merge branch 'master' (early part) into medusa
jump to
@@ -53,6 +53,12 @@ - GBA Timer: Fix timers sometimes being late (fixes mgba.io/i/1012)
- GBA Hardware: Fix RTC overriding light sensor (fixes mgba.io/i/1069) - GBA Savedata: Fix savedata modified time updating when read-only - 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: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722)@@ -77,6 +83,7 @@ - GBA Savedata: Remove ability to disable realistic timing
- Qt: Add load alternate save option - GB Audio: Improved audio quality - GB, GBA Audio: Increase max audio volume + - GB: Fix VRAM/palette locking (fixes mgba.io/i/1109) 0.6.3: (2017-04-14) Bugfixes:
@@ -909,10 +909,6 @@ endif()
if(BUILD_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() if(BUILD_QT)@@ -967,7 +963,7 @@ add_executable(${BINARY_NAME}-example-server ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/example/client-server/server.c)
target_link_libraries(${BINARY_NAME}-example-server ${BINARY_NAME}) 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) 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@@ -1071,12 +1067,12 @@ cpack_add_component_group(qt PARENT_GROUP base)
cpack_add_component(${BINARY_NAME}-qt GROUP qt DEPENDS base) endif() -if(BUILD_SDL) +if(FOUND_SDL) cpack_add_component_group(sdl PARENT_GROUP base) cpack_add_component(${BINARY_NAME}-sdl GROUP sdl DEPENDS base) 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}-fuzz GROUP test DEPENDS dev) cpack_add_component(tbl-fuzz GROUP test DEPENDS dev)
@@ -140,7 +140,7 @@ This will build and install medusa into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them.
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 cd build cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..
@@ -122,7 +122,7 @@ Damit wird mGBA gebaut und in `/usr/bin` und `/usr/lib` installiert. Installierte Abhängigkeiten werden automatisch erkannt. Features, die aufgrund fehlender Abhängigkeiten deaktiviert werden, werden nach dem `cmake`-Kommando aufgelistet.
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 cd build cmake -DCMAKE_PREFIX_PATH='brew --prefix qt5' ..
@@ -136,12 +136,13 @@ uint32_t hi; \
uint32_t lo; \ }; \ uint64_t b64; \ - } *bswap = (void*) &DEST; \ + } bswap; \ const void* _ptr = (ARR); \ __asm__( \ "lwbrx %0, %2, %3 \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) { \@@ -152,12 +153,12 @@ uint32_t hi; \
uint32_t lo; \ }; \ uint64_t b64; \ - } *bswap = (void*) &SRC; \ + } bswap = { .b64 = SRC }; \ const void* _ptr = (ARR); \ __asm__( \ "stwbrx %0, %2, %3 \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)
@@ -33,7 +33,8 @@ bool mCoreSyncWaitFrameStart(struct mCoreSync* sync);
void mCoreSyncWaitFrameEnd(struct mCoreSync* sync); 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 mCoreSyncUnlockAudio(struct mCoreSync* sync); void mCoreSyncConsumeAudio(struct mCoreSync* sync);
@@ -15,7 +15,7 @@ #ifndef BUILD_GLES2
#cmakedefine BUILD_GLES2 #endif -// COLOR flags +// Miscellaneous flags #ifndef COLOR_16_BIT #cmakedefine COLOR_16_BIT@@ -25,6 +25,14 @@ #ifndef COLOR_5_6_5
#cmakedefine COLOR_5_6_5 #endif +#ifndef DISABLE_THREADING +#cmakedefine DISABLE_THREADING +#endif + +#ifndef FIXED_ROM_BUFFER +#cmakedefine FIXED_ROM_BUFFER +#endif + // M_CORE flags #ifndef M_CORE_GBA@@ -39,9 +47,13 @@ #ifndef M_CORE_DS
#cmakedefine M_CORE_DS #endif -// USE flags +// ENABLE flags -#ifndef MINIMAL_CORE +#ifndef ENABLE_SCRIPTING +#cmakedefine ENABLE_SCRIPTING +#endif + +// USE flags #ifndef USE_DEBUGGERS #cmakedefine USE_DEBUGGERS@@ -49,6 +61,10 @@ #endif
#ifndef USE_EDITLINE #cmakedefine USE_EDITLINE +#endif + +#ifndef USE_ELF +#cmakedefine USE_ELF #endif #ifndef USE_EPOXY@@ -97,8 +113,6 @@ #endif
#ifndef USE_ZLIB #cmakedefine USE_ZLIB -#endif - #endif #endif
@@ -5,6 +5,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba/core/sync.h> +#include <mgba/core/blip_buf.h> + static void _changeVideoSync(struct mCoreSync* sync, bool frameOn) { // Make sure the video thread can process events while the GBA thread is paused MutexLock(&sync->videoFrameMutex);@@ -76,16 +78,20 @@
_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) { - return; + return true; } - if (sync->audioWait && wait) { - // TODO loop properly in event of spurious wakeups + size_t produced = blip_samples_avail(buf); + size_t producedNew = produced; + while (sync->audioWait && producedNew >= samples) { ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex); + produced = producedNew; + producedNew = blip_samples_avail(buf); } MutexUnlock(&sync->audioBufferMutex); + return producedNew != produced; } void mCoreSyncLockAudio(struct mCoreSync* sync) {
@@ -5,6 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba/core/thread.h> +#include <mgba/core/blip_buf.h> #include <mgba/core/core.h> #include <mgba/core/serialize.h> #include <mgba-util/patch.h>@@ -219,6 +220,11 @@
deferred = impl->state; while (impl->state >= THREAD_WAITING && impl->state <= THREAD_MAX_WAITING) { 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);
@@ -352,7 +352,7 @@ if (audio->p->stream && audio->p->stream->postAudioFrame) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight); } 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) { audio->p->stream->postAudioBuffer(audio->p->stream, audio->left, audio->right);
@@ -667,7 +667,10 @@ if (audio->p->stream && audio->p->stream->postAudioFrame) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight); } 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) { audio->p->stream->postAudioBuffer(audio->p->stream, audio->left, audio->right);
@@ -644,17 +644,17 @@ nextEvent = mTimingTick(&gb->timing, nextEvent);
} while (gb->cpuBlocked); cpu->nextEvent = nextEvent; - if (gb->earlyExit) { - gb->earlyExit = false; - break; - } if (cpu->halted) { cpu->cycles = cpu->nextEvent; if (!gb->memory.ie || !gb->memory.ime) { break; } } + if (gb->earlyExit) { + break; + } } while (cpu->cycles >= cpu->nextEvent); + gb->earlyExit = false; } void GBSetInterrupts(struct LR35902Core* cpu, bool enable) {
@@ -104,6 +104,8 @@ [REG_SVBK] = 0xF8,
[REG_IE] = 0xE0, }; +static uint8_t _readKeys(struct GB* gb); + static void _writeSGBBits(struct GB* gb, int bits) { if (!bits) { gb->sgbBit = -1;@@ -394,10 +396,12 @@ gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1] = value;
} break; case REG_JOYP: + gb->memory.io[REG_JOYP] = value | 0x0F; + _readKeys(gb); if (gb->model == GB_MODEL_SGB) { _writeSGBBits(gb, (value >> 4) & 3); } - break; + return; case REG_TIMA: if (value && mTimingUntil(&gb->timing, &gb->timer.irq) > 1) { mTimingDeschedule(&gb->timing, &gb->timer.irq);@@ -485,8 +489,10 @@ gb->video.bcpIncrement = value & 0x80;
gb->memory.io[REG_BCPD] = gb->video.palette[gb->video.bcpIndex >> 1] >> (8 * (gb->video.bcpIndex & 1)); break; case REG_BCPD: - GBVideoProcessDots(&gb->video, 0); - GBVideoWritePalette(&gb->video, address, value); + if (gb->video.mode != 3) { + GBVideoProcessDots(&gb->video, 0); + GBVideoWritePalette(&gb->video, address, value); + } return; case REG_OCPS: gb->video.ocpIndex = value & 0x3F;@@ -494,8 +500,10 @@ gb->video.ocpIncrement = value & 0x80;
gb->memory.io[REG_OCPD] = gb->video.palette[8 * 4 + (gb->video.ocpIndex >> 1)] >> (8 * (gb->video.ocpIndex & 1)); break; case REG_OCPD: - GBVideoProcessDots(&gb->video, 0); - GBVideoWritePalette(&gb->video, address, value); + if (gb->video.mode != 3) { + GBVideoProcessDots(&gb->video, 0); + GBVideoWritePalette(&gb->video, address, value); + } return; case REG_SVBK: GBMemorySwitchWramBank(&gb->memory, value);@@ -522,7 +530,8 @@ uint8_t keys = *gb->keySource;
if (gb->sgbCurrentController != 0) { keys = 0; } - switch (gb->memory.io[REG_JOYP] & 0x30) { + uint8_t joyp = gb->memory.io[REG_JOYP]; + switch (joyp & 0x30) { case 0x30: keys = gb->sgbCurrentController; break;@@ -535,7 +544,12 @@ case 0x00:
keys |= keys >> 4; 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) {@@ -639,10 +653,7 @@ return gb->memory.io[address] | _registerMask[address];
} void GBTestKeypadIRQ(struct GB* gb) { - if (_readKeys(gb)) { - gb->memory.io[REG_IF] |= (1 << GB_IRQ_KEYPAD); - GBUpdateIRQs(gb); - } + _readKeys(gb); } struct GBSerializedState;
@@ -239,7 +239,10 @@ case GB_REGION_CART_BANK1 + 1:
return memory->romBank[address & (GB_SIZE_CART_BANK0 - 1)]; case GB_REGION_VRAM: 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 + 1: if (memory->rtcAccess) {@@ -309,8 +312,10 @@ cpu->memory.setActiveRegion(cpu, cpu->pc);
return; case GB_REGION_VRAM: 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)); - gb->video.vramBank[address & (GB_SIZE_VRAM_BANK0 - 1)] = value; + if (gb->video.mode != 3) { + 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; case GB_REGION_EXTERNAL_RAM: case GB_REGION_EXTERNAL_RAM + 1:@@ -490,7 +495,7 @@ gb->memory.hdmaDest &= 0x1FF0;
gb->memory.hdmaDest |= 0x8000; bool wasHdma = gb->memory.isHdma; 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) { gb->memory.hdmaRemaining = 0x10; } else {
@@ -12,7 +12,12 @@ #include <mgba-util/configuration.h>
#include <mgba-util/crc32.h> 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 } } };
@@ -147,7 +147,7 @@ video->renderer->writePalette(video->renderer, 9 * 4 + 3, video->palette[9 * 4 + 3]);
} void GBVideoDeinit(struct GBVideo* video) { - GBVideoAssociateRenderer(video, &dummyRenderer); + video->renderer->deinit(video->renderer); mappedMemoryFree(video->vram, GB_SIZE_VRAM); if (video->renderer->sgbCharRam) { mappedMemoryFree(video->renderer->sgbCharRam, SGB_SIZE_CHAR_RAM);@@ -437,6 +437,7 @@ mTimingDeschedule(&video->p->timing, &video->frameEvent);
} if (GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && !GBRegisterLCDCIsEnable(value)) { // TODO: Fix serialization; this gets internal and visible modes out of sync + video->mode = 0; video->stat = GBRegisterSTATSetMode(video->stat, 0); video->p->memory.io[REG_STAT] = video->stat; video->ly = 0;
@@ -307,7 +307,10 @@ if (audio->p->stream && audio->p->stream->postAudioFrame) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight); } 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) { audio->p->stream->postAudioBuffer(audio->p->stream, audio->psg.left, audio->psg.right);
@@ -809,7 +809,7 @@ uint8_t in = 0;
uint32_t out = 0; int bitsRemaining = 0; int bitsEaten = 0; - while (sourceLen > 0) { + while (sourceLen > 0 || bitsRemaining) { if (!bitsRemaining) { in = cpu->memory.load8(cpu, source, 0); bitsRemaining = 8;
@@ -264,11 +264,6 @@ nextEvent = mTimingTick(&gba->timing, nextEvent + cycles);
} while (gba->cpuBlocked); cpu->nextEvent = nextEvent; - - if (gba->earlyExit) { - gba->earlyExit = false; - break; - } if (cpu->halted) { cpu->cycles = nextEvent; if (!gba->memory.io[REG_IME >> 1] || !gba->memory.io[REG_IE >> 1]) {@@ -280,7 +275,11 @@ else if (nextEvent < 0) {
mLOG(GBA, FATAL, "Negative cycles will pass: %i", nextEvent); } #endif + if (gba->earlyExit) { + break; + } } + gba->earlyExit = false; #ifndef NDEBUG if (gba->cpuBlocked) { mLOG(GBA, FATAL, "CPU is blocked!");
@@ -293,7 +293,7 @@ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, // Audio 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
@@ -163,6 +163,9 @@ }
value &= ~0x0080; } break; + case SIO_MULTI: + value |= 0xC; + break; default: // TODO break;
@@ -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 * License, v. 2.0. If a copy of the MPL was not distributed with this@@ -11,6 +11,8 @@
#define TIMER_IRQ_DELAY 7 #define TIMER_RELOAD_DELAY 0 #define TIMER_STARTUP_DELAY 2 + +#define REG_TMCNT_LO(X) (REG_TM0CNT_LO + ((X) << 2)) static void GBATimerIrq(struct GBA* gba, int timerId) { struct GBATimer* timer = &gba->timers[timerId];@@ -45,12 +47,11 @@ GBATimerIrq(context, 3);
} void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) { - *io = timer->reload; - int32_t currentTime = mTimingCurrentTime(timing) - cyclesLate; - int32_t tickMask = (1 << GBATimerFlagsGetPrescaleBits(timer->flags)) - 1; - currentTime &= ~tickMask; - timer->lastEvent = currentTime; - GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_RELOAD_DELAY + cyclesLate); + if (GBATimerFlagsIsCountUp(timer->flags)) { + *io = timer->reload; + } else { + GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_RELOAD_DELAY + cyclesLate); + } if (GBATimerFlagsIsDoIrq(timer->flags)) { timer->flags = GBATimerFlagsFillIrqPending(timer->flags);@@ -159,20 +160,30 @@ if (!GBATimerFlagsIsEnable(timer->flags) || GBATimerFlagsIsCountUp(timer->flags)) {
return; } + // Align timer int prescaleBits = GBATimerFlagsGetPrescaleBits(timer->flags); int32_t currentTime = mTimingCurrentTime(timing) - skew; int32_t tickMask = (1 << prescaleBits) - 1; currentTime &= ~tickMask; + + // Update register int32_t tickIncrement = currentTime - timer->lastEvent; timer->lastEvent = currentTime; tickIncrement >>= prescaleBits; tickIncrement += *io; *io = tickIncrement; - if (!mTimingIsScheduled(timing, &timer->event)) { - tickIncrement = (0x10000 - tickIncrement) << prescaleBits; - currentTime -= mTimingCurrentTime(timing) - skew; - mTimingSchedule(timing, &timer->event, tickIncrement + currentTime); + while (tickIncrement >= 0x10000) { + tickIncrement -= 0x10000 - timer->reload; } + *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) {
@@ -80,16 +80,18 @@ video->event.priority = 8;
} void GBAVideoReset(struct GBAVideo* video) { + int32_t nextEvent = VIDEO_HDRAW_LENGTH; if (video->p->memory.fullBios) { video->vcount = 0; } else { - // TODO: Verify exact scanline hardware + // TODO: Verify exact scanline on hardware video->vcount = 0x7E; + nextEvent = 170; } video->p->memory.io[REG_VCOUNT >> 1] = video->vcount; video->event.callback = _startHblank; - mTimingSchedule(&video->p->timing, &video->event, VIDEO_HDRAW_LENGTH); + mTimingSchedule(&video->p->timing, &video->event, nextEvent); video->frameCounter = 0; video->frameskipCounter = 0;@@ -113,7 +115,7 @@ video->renderer->init(video->renderer);
} void GBAVideoDeinit(struct GBAVideo* video) { - GBAVideoAssociateRenderer(video, &dummyRenderer); + video->renderer->deinit(video->renderer); mappedMemoryFree(video->vram, SIZE_VRAM); }
@@ -99,6 +99,7 @@
void LR35902DebuggerInit(void* cpu, struct mDebuggerPlatform* platform) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) platform; debugger->cpu = cpu; + debugger->originalMemory = debugger->cpu->memory; LR35902DebugBreakpointListInit(&debugger->breakpoints, 0); LR35902DebugWatchpointListInit(&debugger->watchpoints, 0); }
@@ -0,0 +1,2 @@
+[MESSAGES CONTROL] +disable=line-too-long,missing-docstring,too-few-public-methods,too-many-instance-attributes,too-many-public-methods,wrong-import-order
@@ -11,15 +11,6 @@ endforeach()
file(GLOB PYTHON_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.h) -if(NOT GIT_TAG) - if(GIT_BRANCH STREQUAL "master" OR NOT GIT_BRANCH) - set(PYLIB_VERSION -b .dev${GIT_REV}+g${GIT_COMMIT_SHORT}) - else() - set(PYLIB_VERSION -b .dev${GIT_REV}+${GIT_BRANCH}.g${GIT_COMMIT_SHORT}) - endif() -endif() -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lib.c COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/lib.c@@ -35,15 +26,29 @@ set_target_properties(${BINARY_NAME}-pylib PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_BINARY_DIR};${INCLUDE_DIRECTORIES}")
set_target_properties(${BINARY_NAME}-pylib PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") add_custom_target(${BINARY_NAME}-py ALL - COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py egg_info -e ${CMAKE_CURRENT_BINARY_DIR} ${PYLIB_VERSION} - COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${BINARY_NAME} - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/setup.py + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/setup.py DEPENDS ${PYTHON_HEADERS} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py DEPENDS ${BINARY_NAME}-pylib) +add_custom_target(${BINARY_NAME}-py-install + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py install -b ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${BINARY_NAME}-py) + +add_custom_target(${BINARY_NAME}-py-develop + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py develop -b ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${BINARY_NAME}-py) + +add_custom_target(${BINARY_NAME}-py-bdist + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py bdist_wheel -b ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${BINARY_NAME}-py) + file(GLOB BASE_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/test_*.py) file(GLOB SUBTESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/*/test_*.py) foreach(TEST IN LISTS BASE_TESTS SUBTESTS)@@ -56,6 +61,8 @@ set(PATH LD_LIBRARY_PATH)
endif() string(REGEX REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/(tests/.*/)?test_" "" TEST_NAME "${TEST}") string(REPLACE ".py" "" TEST_NAME "${TEST_NAME}") - add_test(python-${TEST_NAME} ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} pytest --extras --addopts ${TEST}) - set_tests_properties(python-${TEST_NAME} PROPERTIES ENVIRONMENT "${PATH}=${CMAKE_CURRENT_BINARY_DIR}/..") + add_test(NAME python-${TEST_NAME} + COMMAND ${PYTHON_EXECUTABLE} setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} pytest --extras --addopts ${TEST} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + set_tests_properties(python-${TEST_NAME} PROPERTIES ENVIRONMENT "${PATH}=${CMAKE_CURRENT_BINARY_DIR}/..;BINDIR=${CMAKE_CURRENT_BINARY_DIR}/..;LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/..;CPPFLAGS=${INCLUDE_FLAGS}") endforeach()
@@ -80,10 +80,6 @@ ffi.embedding_api('\n'.join(lines))
ffi.embedding_init_code(""" import os, os.path - venv = os.getenv('VIRTUAL_ENV') - if venv: - activate = os.path.join(venv, 'bin', 'activate_this.py') - exec(compile(open(activate, "rb").read(), activate, 'exec'), dict(__file__=activate)) from mgba._pylib import ffi, lib symbols = {} globalSyms = {@@ -111,7 +107,7 @@ def mPythonLoadScript(name, vf):
from mgba.vfs import VFile vf = VFile(vf) name = ffi.string(name) - source = vf.readAll().decode('utf-8') + source = vf.read_all().decode('utf-8') try: code = compile(source, name, 'exec') pendingCode.append(code)
@@ -2,15 +2,16 @@ from PIL.ImageChops import difference
from PIL.ImageOps import autocontrast from PIL.Image import open as PIOpen + class VideoFrame(object): - def __init__(self, pilImage): - self.image = pilImage.convert('RGB') + def __init__(self, pil_image): + self.image = pil_image.convert('RGB') @staticmethod def diff(a, b): diff = difference(a.image, b.image) - diffNormalized = autocontrast(diff) - return (VideoFrame(diff), VideoFrame(diffNormalized)) + diff_normalized = autocontrast(diff) + return (VideoFrame(diff), VideoFrame(diff_normalized)) @staticmethod def load(path):
@@ -4,44 +4,38 @@ from . import VideoFrame
Output = namedtuple('Output', ['video']) + class Tracer(object): def __init__(self, core): self.core = core - self.fb = Image(*core.desiredVideoDimensions()) - self.core.setVideoBuffer(self.fb) - self._videoFifo = [] + self.framebuffer = Image(*core.desired_video_dimensions()) + self.core.set_video_buffer(self.framebuffer) + self._video_fifo = [] - def yieldFrames(self, skip=0, limit=None): + def yield_frames(self, skip=0, limit=None): self.core.reset() skip = (skip or 0) + 1 while skip > 0: - frame = self.core.frameCounter - self.core.runFrame() + frame = self.core.frame_counter + self.core.run_frame() skip -= 1 - while frame <= self.core.frameCounter and limit != 0: - self._videoFifo.append(VideoFrame(self.fb.toPIL())) + while frame <= self.core.frame_counter and limit != 0: + self._video_fifo.append(VideoFrame(self.framebuffer.to_pil())) yield frame - frame = self.core.frameCounter - self.core.runFrame() + frame = self.core.frame_counter + self.core.run_frame() if limit is not None: assert limit >= 0 limit -= 1 def video(self, generator=None, **kwargs): if not generator: - generator = self.yieldFrames(**kwargs) + generator = self.yield_frames(**kwargs) try: while True: - if self._videoFifo: - result = self._videoFifo[0] - self._videoFifo = self._videoFifo[1:] - yield result + if self._video_fifo: + yield self._video_fifo.pop(0) else: next(generator) except StopIteration: return - - def output(self, **kwargs): - generator = self.yieldFrames(**kwargs) - - return mCoreOutput(video=self.video(generator=generator, **kwargs))
@@ -1,5 +1,7 @@
-import os, os.path -import mgba.core, mgba.image +import os +import os.path +import mgba.core +import mgba.image import cinema.movie import itertools import glob@@ -7,20 +9,21 @@ import re
import yaml from copy import deepcopy from cinema import VideoFrame -from cinema.util import dictMerge +from cinema.util import dict_merge + class CinemaTest(object): TEST = 'test.(mvl|gb|gba|nds)' def __init__(self, path, root, settings={}): - self.fullPath = path or [] - self.path = os.path.abspath(os.path.join(root, *self.fullPath)) + self.full_path = path or [] + self.path = os.path.abspath(os.path.join(root, *self.full_path)) self.root = root self.name = '.'.join(path) self.settings = settings try: with open(os.path.join(self.path, 'manifest.yml'), 'r') as f: - dictMerge(self.settings, yaml.safe_load(f)) + dict_merge(self.settings, yaml.safe_load(f)) except IOError: pass self.tests = {}@@ -28,41 +31,42 @@
def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self.name) - def setUp(self): + def setup(self): results = [f for f in glob.glob(os.path.join(self.path, 'test.*')) if re.search(self.TEST, f)] - self.core = mgba.core.loadPath(results[0]) + self.core = mgba.core.load_path(results[0]) if 'config' in self.settings: self.config = mgba.core.Config(defaults=self.settings['config']) - self.core.loadConfig(self.config) + self.core.load_config(self.config) self.core.reset() - def addTest(self, name, cls=None, settings={}): + def add_test(self, name, cls=None, settings={}): cls = cls or self.__class__ - newSettings = deepcopy(self.settings) - dictMerge(newSettings, settings) - self.tests[name] = cls(self.fullPath + [name], self.root, newSettings) + new_settings = deepcopy(self.settings) + dict_merge(new_settings, settings) + self.tests[name] = cls(self.full_path + [name], self.root, new_settings) return self.tests[name] - def outputSettings(self): - outputSettings = {} + def output_settings(self): + output_settings = {} if 'frames' in self.settings: - outputSettings['limit'] = self.settings['frames'] + output_settings['limit'] = self.settings['frames'] if 'skip' in self.settings: - outputSettings['skip'] = self.settings['skip'] - return outputSettings + output_settings['skip'] = self.settings['skip'] + return output_settings def __lt__(self, other): return self.path < other.path + class VideoTest(CinemaTest): BASELINE = 'baseline_%04u.png' - def setUp(self): - super(VideoTest, self).setUp(); + def setup(self): + super(VideoTest, self).setup() self.tracer = cinema.movie.Tracer(self.core) - def generateFrames(self): - for i, frame in zip(itertools.count(), self.tracer.video(**self.outputSettings())): + def generate_frames(self): + for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())): try: baseline = VideoFrame.load(os.path.join(self.path, self.BASELINE % i)) yield baseline, frame, VideoFrame.diff(baseline, frame)@@ -70,14 +74,15 @@ except IOError:
yield None, frame, (None, None) def test(self): - self.baseline, self.frames, self.diffs = zip(*self.generateFrames()) + self.baseline, self.frames, self.diffs = zip(*self.generate_frames()) assert not any(any(diffs[0].image.convert("L").point(bool).getdata()) for diffs in self.diffs) - def generateBaseline(self): - for i, frame in zip(itertools.count(), self.tracer.video(**self.outputSettings())): + def generate_baseline(self): + for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())): frame.save(os.path.join(self.path, self.BASELINE % i)) -def gatherTests(root=os.getcwd()): + +def gather_tests(root=os.getcwd()): tests = CinemaTest([], root) for path, _, files in os.walk(root): test = [f for f in files if re.match(CinemaTest.TEST, f)]@@ -85,12 +90,12 @@ if not test:
continue prefix = os.path.commonprefix([path, root]) suffix = path[len(prefix)+1:] - testPath = suffix.split(os.sep) - testRoot = tests - for component in testPath[:-1]: - newTest = testRoot.tests.get(component) - if not newTest: - newTest = testRoot.addTest(component) - testRoot = newTest - testRoot.addTest(testPath[-1], VideoTest) + test_path = suffix.split(os.sep) + test_root = tests + for component in test_path[:-1]: + new_test = test_root.tests.get(component) + if not new_test: + new_test = test_root.add_test(component) + test_root = new_test + test_root.add_test(test_path[-1], VideoTest) return tests
@@ -1,8 +1,8 @@
-def dictMerge(a, b): +def dict_merge(a, b): for key, value in b.items(): if isinstance(value, dict): if key in a: - dictMerge(a[key], value) + dict_merge(a[key], value) else: a[key] = dict(value) else:
@@ -3,31 +3,34 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from collections import namedtuple -def createCallback(structName, cbName, funcName=None): - funcName = funcName or "_py{}{}".format(structName, cbName[0].upper() + cbName[1:]) - fullStruct = "struct {}*".format(structName) - def cb(handle, *args): - h = ffi.cast(fullStruct, handle) - return getattr(ffi.from_handle(h.pyobj), cbName)(*args) + +def create_callback(struct_name, cb_name, func_name=None): + func_name = func_name or "_py{}{}".format(struct_name, cb_name[0].upper() + cb_name[1:]) + full_struct = "struct {}*".format(struct_name) + + def callback(handle, *args): + handle = ffi.cast(full_struct, handle) + return getattr(ffi.from_handle(handle.pyobj), cb_name)(*args) + + return ffi.def_extern(name=func_name)(callback) - return ffi.def_extern(name=funcName)(cb) -version = ffi.string(lib.projectVersion).decode('utf-8') +__version__ = ffi.string(lib.projectVersion).decode('utf-8') GitInfo = namedtuple("GitInfo", "commit commitShort branch revision") -git = {} +GIT = {} if lib.gitCommit and lib.gitCommit != "(unknown)": - git['commit'] = ffi.string(lib.gitCommit).decode('utf-8') + GIT['commit'] = ffi.string(lib.gitCommit).decode('utf-8') if lib.gitCommitShort and lib.gitCommitShort != "(unknown)": - git['commitShort'] = ffi.string(lib.gitCommitShort).decode('utf-8') + GIT['commitShort'] = ffi.string(lib.gitCommitShort).decode('utf-8') if lib.gitBranch and lib.gitBranch != "(unknown)": - git['branch'] = ffi.string(lib.gitBranch).decode('utf-8') + GIT['branch'] = ffi.string(lib.gitBranch).decode('utf-8') if lib.gitRevision > 0: - git['revision'] = lib.gitRevision + GIT['revision'] = lib.gitRevision -git = GitInfo(**git) +GIT = GitInfo(**GIT)
@@ -3,21 +3,23 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module + class _ARMRegisters: def __init__(self, cpu): self._cpu = cpu - def __getitem__(self, r): - if r > lib.ARM_PC: + def __getitem__(self, reg): + if reg > lib.ARM_PC: raise IndexError("Register out of range") - return self._cpu._native.gprs[r] + return self._cpu._native.gprs[reg] - def __setitem__(self, r, value): - if r >= lib.ARM_PC: + def __setitem__(self, reg, value): + if reg >= lib.ARM_PC: raise IndexError("Register out of range") - self._cpu._native.gprs[r] = value + self._cpu._native.gprs[reg] = value + class ARMCore: def __init__(self, native):@@ -25,4 +27,3 @@ self._native = ffi.cast("struct ARMCore*", native)
self.gprs = _ARMRegisters(self) self.cpsr = self._native.cpsr self.spsr = self._native.spsr -
@@ -3,9 +3,11 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib -from . import tile, createCallback +from ._pylib import ffi, lib # pylint: disable=no-name-in-module +from . import tile from cached_property import cached_property +from functools import wraps + def find(path): core = lib.mCoreFind(path.encode('UTF-8'))@@ -13,82 +15,95 @@ if core == ffi.NULL:
return None return Core._init(core) -def findVF(vf): - core = lib.mCoreFindVF(vf.handle) + +def find_vf(vfile): + core = lib.mCoreFindVF(vfile.handle) if core == ffi.NULL: return None return Core._init(core) -def loadPath(path): + +def load_path(path): core = find(path) - if not core or not core.loadFile(path): + if not core or not core.load_file(path): return None return core -def loadVF(vf): - core = findVF(vf) - if not core or not core.loadROM(vf): + +def load_vf(vfile): + core = find_vf(vfile) + if not core or not core.load_rom(vfile): return None return core -def needsReset(f): + +def needs_reset(func): + @wraps(func) def wrapper(self, *args, **kwargs): - if not self._wasReset: + if not self._was_reset: raise RuntimeError("Core must be reset first") - return f(self, *args, **kwargs) + return func(self, *args, **kwargs) return wrapper -def protected(f): + +def protected(func): + @wraps(func) def wrapper(self, *args, **kwargs): if self._protected: raise RuntimeError("Core is protected") - return f(self, *args, **kwargs) + return func(self, *args, **kwargs) return wrapper + @ffi.def_extern() -def _mCorePythonCallbacksVideoFrameStarted(user): +def _mCorePythonCallbacksVideoFrameStarted(user): # pylint: disable=invalid-name context = ffi.from_handle(user) - context._videoFrameStarted() + context._video_frame_started() + @ffi.def_extern() -def _mCorePythonCallbacksVideoFrameEnded(user): +def _mCorePythonCallbacksVideoFrameEnded(user): # pylint: disable=invalid-name context = ffi.from_handle(user) - context._videoFrameEnded() + context._video_frame_ended() + @ffi.def_extern() -def _mCorePythonCallbacksCoreCrashed(user): +def _mCorePythonCallbacksCoreCrashed(user): # pylint: disable=invalid-name context = ffi.from_handle(user) - context._coreCrashed() + context._core_crashed() + @ffi.def_extern() -def _mCorePythonCallbacksSleep(user): +def _mCorePythonCallbacksSleep(user): # pylint: disable=invalid-name context = ffi.from_handle(user) context._sleep() + class CoreCallbacks(object): def __init__(self): self._handle = ffi.new_handle(self) - self.videoFrameStarted = [] - self.videoFrameEnded = [] - self.coreCrashed = [] + self.video_frame_started = [] + self.video_frame_ended = [] + self.core_crashed = [] self.sleep = [] self.context = lib.mCorePythonCallbackCreate(self._handle) - def _videoFrameStarted(self): - for cb in self.videoFrameStarted: - cb() + def _video_frame_started(self): + for callback in self.video_frame_started: + callback() - def _videoFrameEnded(self): - for cb in self.videoFrameEnded: - cb() + def _video_frame_ended(self): + for callback in self.video_frame_ended: + callback() - def _coreCrashed(self): - for cb in self.coreCrashed: - cb() + def _core_crashed(self): + for callback in self.core_crashed: + callback() def _sleep(self): - for cb in self.sleep: - cb() + for callback in self.sleep: + callback() + class Core(object): if hasattr(lib, 'PLATFORM_GBA'):@@ -102,36 +117,36 @@ PLATFORM_GB = lib.PLATFORM_DS
def __init__(self, native): self._core = native - self._wasReset = False + self._was_reset = False self._protected = False self._callbacks = CoreCallbacks() self._core.addCoreCallbacks(self._core, self._callbacks.context) self.config = Config(ffi.addressof(native.config)) def __del__(self): - self._wasReset = False + self._was_reset = False @cached_property - def graphicsCache(self): - if not self._wasReset: + def graphics_cache(self): + if not self._was_reset: raise RuntimeError("Core must be reset first") return tile.CacheSet(self) @cached_property def tiles(self): - t = [] - ts = ffi.addressof(self.graphicsCache.cache.tiles) - for i in range(lib.mTileCacheSetSize(ts)): - t.append(tile.TileView(lib.mTileCacheSetGetPointer(ts, i))) - return t + tiles = [] + native_tiles = ffi.addressof(self.graphics_cache.cache.tiles) + for i in range(lib.mTileCacheSetSize(native_tiles)): + tiles.append(tile.TileView(lib.mTileCacheSetGetPointer(native_tiles, i))) + return tiles @cached_property def maps(self): - m = [] - ms = ffi.addressof(self.graphicsCache.cache.maps) - for i in range(lib.mMapCacheSetSize(ms)): - m.append(tile.MapView(lib.mMapCacheSetGetPointer(ms, i))) - return m + maps = [] + native_maps = ffi.addressof(self.graphics_cache.cache.maps) + for i in range(lib.mMapCacheSetSize(native_maps)): + maps.append(tile.MapView(lib.mMapCacheSetGetPointer(native_maps, i))) + return maps @classmethod def _init(cls, native):@@ -156,73 +171,73 @@ return DS(core)
return Core(core) def _load(self): - self._wasReset = True + self._was_reset = True - def loadFile(self, path): + def load_file(self, path): return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8'))) - def isROM(self, vf): - return bool(self._core.isROM(vf.handle)) + def is_rom(self, vfile): + return bool(self._core.isROM(vfile.handle)) - def loadROM(self, vf): - return bool(self._core.loadROM(self._core, vf.handle)) + def load_rom(self, vfile): + return bool(self._core.loadROM(self._core, vfile.handle)) - def loadBIOS(self, vf, id=0): - return bool(self._core.loadBIOS(self._core, vf.handle, id)) + def load_bios(self, vfile, id=0): + return bool(self._core.loadBIOS(self._core, vfile.handle, id)) - def loadSave(self, vf): - return bool(self._core.loadSave(self._core, vf.handle)) + def load_save(self, vfile): + return bool(self._core.loadSave(self._core, vfile.handle)) - def loadTemporarySave(self, vf): - return bool(self._core.loadTemporarySave(self._core, vf.handle)) + def load_temporary_save(self, vfile): + return bool(self._core.loadTemporarySave(self._core, vfile.handle)) - def loadPatch(self, vf): - return bool(self._core.loadPatch(self._core, vf.handle)) + def load_patch(self, vfile): + return bool(self._core.loadPatch(self._core, vfile.handle)) - def loadConfig(self, config): + def load_config(self, config): lib.mCoreLoadForeignConfig(self._core, config._native) - def autoloadSave(self): + def autoload_save(self): return bool(lib.mCoreAutoloadSave(self._core)) - def autoloadPatch(self): + def autoload_patch(self): return bool(lib.mCoreAutoloadPatch(self._core)) - def autoloadCheats(self): + def autoload_cheats(self): return bool(lib.mCoreAutoloadCheats(self._core)) def platform(self): return self._core.platform(self._core) - def desiredVideoDimensions(self): + def desired_video_dimensions(self): width = ffi.new("unsigned*") height = ffi.new("unsigned*") self._core.desiredVideoDimensions(self._core, width, height) return width[0], height[0] - def setVideoBuffer(self, image): + def set_video_buffer(self, image): self._core.setVideoBuffer(self._core, image.buffer, image.stride) def reset(self): self._core.reset(self._core) self._load() - @needsReset + @needs_reset @protected - def runFrame(self): + def run_frame(self): self._core.runFrame(self._core) - @needsReset + @needs_reset @protected - def runLoop(self): + def run_loop(self): self._core.runLoop(self._core) - @needsReset + @needs_reset def step(self): self._core.step(self._core) @staticmethod - def _keysToInt(*args, **kwargs): + def _keys_to_int(*args, **kwargs): keys = 0 if 'raw' in kwargs: keys = kwargs['raw']@@ -230,22 +245,25 @@ for key in args:
keys |= 1 << key return keys - def setKeys(self, *args, **kwargs): - self._core.setKeys(self._core, self._keysToInt(*args, **kwargs)) + @protected + def set_keys(self, *args, **kwargs): + self._core.setKeys(self._core, self._keys_to_int(*args, **kwargs)) - def addKeys(self, *args, **kwargs): - self._core.addKeys(self._core, self._keysToInt(*args, **kwargs)) + @protected + def add_keys(self, *args, **kwargs): + self._core.addKeys(self._core, self._keys_to_int(*args, **kwargs)) - def clearKeys(self, *args, **kwargs): - self._core.clearKeys(self._core, self._keysToInt(*args, **kwargs)) + @protected + def clear_keys(self, *args, **kwargs): + self._core.clearKeys(self._core, self._keys_to_int(*args, **kwargs)) @property - @needsReset - def frameCounter(self): + @needs_reset + def frame_counter(self): return self._core.frameCounter(self._core) @property - def frameCycles(self): + def frame_cycles(self): return self._core.frameCycles(self._core) @property@@ -253,23 +271,24 @@ def frequency(self):
return self._core.frequency(self._core) @property - def gameTitle(self): + def game_title(self): title = ffi.new("char[16]") self._core.getGameTitle(self._core, title) return ffi.string(title, 16).decode("ascii") @property - def gameCode(self): + def game_code(self): code = ffi.new("char[12]") self._core.getGameCode(self._core, code) return ffi.string(code, 12).decode("ascii") - def addFrameCallback(self, cb): - self._callbacks.videoFrameEnded.append(cb) + def add_frame_callback(self, callback): + self._callbacks.video_frame_ended.append(callback) @property def crc32(self): return self._native.romCrc32 + class ICoreOwner(object): def claim(self):@@ -287,6 +306,7 @@ def __exit__(self, type, value, traceback):
self.core._protected = False self.release() + class IRunner(object): def pause(self): raise NotImplementedError@@ -294,14 +314,17 @@
def unpause(self): raise NotImplementedError - def useCore(self): + def use_core(self): raise NotImplementedError - def isRunning(self): + @property + def running(self): raise NotImplementedError - def isPaused(self): + @property + def paused(self): raise NotImplementedError + class Config(object): def __init__(self, native=None, port=None, defaults={}):
@@ -3,25 +3,26 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from .core import IRunner, ICoreOwner, Core -import io import sys + class DebuggerCoreOwner(ICoreOwner): def __init__(self, debugger): self.debugger = debugger - self.wasPaused = False + self.was_paused = False def claim(self): if self.debugger.isRunning(): - self.wasPaused = True + self.was_paused = True self.debugger.pause() return self.debugger._core def release(self): - if self.wasPaused: + if self.was_paused: self.debugger.unpause() + class NativeDebugger(IRunner): WATCHPOINT_WRITE = lib.WATCHPOINT_WRITE@@ -49,37 +50,40 @@
def unpause(self): self._native.state = lib.DEBUGGER_RUNNING - def isRunning(self): + @property + def running(self): return self._native.state == lib.DEBUGGER_RUNNING - def isPaused(self): + @property + def paused(self): return self._native.state in (lib.DEBUGGER_PAUSED, lib.DEBUGGER_CUSTOM) - def useCore(self): + def use_core(self): return DebuggerCoreOwner(self) - def setBreakpoint(self, address): + def set_breakpoint(self, address): if not self._native.platform.setBreakpoint: raise RuntimeError("Platform does not support breakpoints") self._native.platform.setBreakpoint(self._native.platform, address) - def clearBreakpoint(self, address): + def clear_breakpoint(self, address): if not self._native.platform.setBreakpoint: raise RuntimeError("Platform does not support breakpoints") self._native.platform.clearBreakpoint(self._native.platform, address) - def setWatchpoint(self, address): + def set_watchpoint(self, address): if not self._native.platform.setWatchpoint: raise RuntimeError("Platform does not support watchpoints") self._native.platform.setWatchpoint(self._native.platform, address) - def clearWatchpoint(self, address): + def clear_watchpoint(self, address): if not self._native.platform.clearWatchpoint: raise RuntimeError("Platform does not support watchpoints") self._native.platform.clearWatchpoint(self._native.platform, address) - def addCallback(self, cb): - self._cbs.append(cb) + def add_callback(self, callback): + self._cbs.append(callback) + class CLIBackend(object): def __init__(self, backend):@@ -87,6 +91,7 @@ self.backend = backend
def write(self, string): self.backend.printf(string) + class CLIDebugger(NativeDebugger): def __init__(self, native):@@ -97,5 +102,5 @@ def printf(self, message, *args, **kwargs):
message = message.format(*args, **kwargs) self._cli.backend.printf(ffi.new("char []", b"%s"), ffi.new("char []", message.encode('utf-8'))) - def installPrint(self): + def install_print(self): sys.stdout = CLIBackend(self)
@@ -8,6 +8,7 @@ import mgba_gamedata
except ImportError: pass + def search(core): crc32 = None if hasattr(core, 'PLATFORM_GBA') and core.platform() == core.PLATFORM_GBA:
@@ -3,12 +3,13 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from .lr35902 import LR35902Core -from .core import Core, needsReset +from .core import Core, needs_reset from .memory import Memory from .tile import Sprite -from . import createCallback +from . import create_callback + class GB(Core): KEY_A = lib.GBA_KEY_A@@ -25,31 +26,34 @@ super(GB, self).__init__(native)
self._native = ffi.cast("struct GB*", native.board) self.sprites = GBObjs(self) self.cpu = LR35902Core(self._core.cpu) + self.memory = None - @needsReset - def _initCache(self, cache): + @needs_reset + def _init_cache(self, cache): lib.GBVideoCacheInit(cache) lib.GBVideoCacheAssociate(cache, ffi.addressof(self._native.video)) - def _deinitCache(self, cache): + def _deinit_cache(self, cache): lib.mCacheSetDeinit(cache) - if self._wasReset: + if self._was_reset: self._native.video.renderer.cache = ffi.NULL def _load(self): super(GB, self)._load() self.memory = GBMemory(self._core) - def attachSIO(self, link): + def attach_sio(self, link): lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native) def __del__(self): lib.GBSIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL) -createCallback("GBSIOPythonDriver", "init") -createCallback("GBSIOPythonDriver", "deinit") -createCallback("GBSIOPythonDriver", "writeSB") -createCallback("GBSIOPythonDriver", "writeSC") + +create_callback("GBSIOPythonDriver", "init") +create_callback("GBSIOPythonDriver", "deinit") +create_callback("GBSIOPythonDriver", "writeSB") +create_callback("GBSIOPythonDriver", "writeSC") + class GBSIODriver(object): def __init__(self):@@ -62,53 +66,55 @@
def deinit(self): pass - def writeSB(self, value): + def write_sb(self, value): pass - def writeSC(self, value): + def write_sc(self, value): return value + class GBSIOSimpleDriver(GBSIODriver): def __init__(self, period=0x100): super(GBSIOSimpleDriver, self).__init__() - self.rx = 0x00 + self.rx = 0x00 # pylint: disable=invalid-name self._period = period def init(self): self._native.p.period = self._period return True - def writeSB(self, value): - self.rx = value + def write_sb(self, value): + self.rx = value # pylint: disable=invalid-name - def writeSC(self, value): + def write_sc(self, value): self._native.p.period = self._period if value & 0x80: lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event)) lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), self._native.p.period) return value - def isReady(self): + def is_ready(self): return not self._native.p.remainingBits @property - def tx(self): - self._native.p.pendingSB + def tx(self): # pylint: disable=invalid-name + return self._native.p.pendingSB @property def period(self): return self._native.p.period @tx.setter - def tx(self, newTx): + def tx(self, newTx): # pylint: disable=invalid-name self._native.p.pendingSB = newTx self._native.p.remainingBits = 8 @period.setter - def period(self, newPeriod): - self._period = newPeriod + def period(self, new_period): + self._period = new_period if self._native.p: - self._native.p.period = newPeriod + self._native.p.period = new_period + class GBMemory(Memory): def __init__(self, core):@@ -119,15 +125,16 @@ self.vram = Memory(core, lib.GB_SIZE_VRAM, lib.GB_BASE_VRAM)
self.sram = Memory(core, lib.GB_SIZE_EXTERNAL_RAM, lib.GB_REGION_EXTERNAL_RAM) self.iwram = Memory(core, lib.GB_SIZE_WORKING_RAM_BANK0, lib.GB_BASE_WORKING_RAM_BANK0) self.oam = Memory(core, lib.GB_SIZE_OAM, lib.GB_BASE_OAM) - self.io = Memory(core, lib.GB_SIZE_IO, lib.GB_BASE_IO) + self.io = Memory(core, lib.GB_SIZE_IO, lib.GB_BASE_IO) # pylint: disable=invalid-name self.hram = Memory(core, lib.GB_SIZE_HRAM, lib.GB_BASE_HRAM) + class GBSprite(Sprite): - PALETTE_BASE = 8, + PALETTE_BASE = (8,) def __init__(self, obj, core): - self.x = obj.x - self.y = obj.y + self.x = obj.x # pylint: disable=invalid-name + self.y = obj.y # pylint: disable=invalid-name self.tile = obj.tile self._attr = obj.attr self.width = 8@@ -136,10 +143,10 @@ self.height = 16 if lcdc & 4 else 8
if core._native.model >= lib.GB_MODEL_CGB: if self._attr & 8: self.tile += 512 - self.paletteId = self._attr & 7 + self.palette_id = self._attr & 7 else: - self.paletteId = (self._attr >> 4) & 1 - self.paletteId += 8 + self.palette_id = (self._attr >> 4) & 1 + self.palette_id += 8 class GBObjs:
@@ -3,12 +3,13 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from .arm import ARMCore -from .core import Core, needsReset +from .core import Core, needs_reset from .tile import Sprite from .memory import Memory -from . import createCallback +from . import create_callback + class GBA(Core): KEY_A = lib.GBA_KEY_A@@ -34,23 +35,24 @@ super(GBA, self).__init__(native)
self._native = ffi.cast("struct GBA*", native.board) self.sprites = GBAObjs(self) self.cpu = ARMCore(self._core.cpu) + self.memory = None self._sio = set() - @needsReset - def _initCache(self, cache): + @needs_reset + def _init_cache(self, cache): lib.GBAVideoCacheInit(cache) lib.GBAVideoCacheAssociate(cache, ffi.addressof(self._native.video)) - def _deinitCache(self, cache): + def _deinit_cache(self, cache): lib.mCacheSetDeinit(cache) - if self._wasReset: + if self._was_reset: self._native.video.renderer.cache = ffi.NULL def _load(self): super(GBA, self)._load() self.memory = GBAMemory(self._core, self._native.memory.romSize) - def attachSIO(self, link, mode=lib.SIO_MULTI): + def attach_sio(self, link, mode=lib.SIO_MULTI): self._sio.add(mode) lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode)@@ -58,14 +60,18 @@ def __del__(self):
for mode in self._sio: lib.GBASIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL, mode) -createCallback("GBASIOPythonDriver", "init") -createCallback("GBASIOPythonDriver", "deinit") -createCallback("GBASIOPythonDriver", "load") -createCallback("GBASIOPythonDriver", "unload") -createCallback("GBASIOPythonDriver", "writeRegister") + +create_callback("GBASIOPythonDriver", "init") +create_callback("GBASIOPythonDriver", "deinit") +create_callback("GBASIOPythonDriver", "load") +create_callback("GBASIOPythonDriver", "unload") +create_callback("GBASIOPythonDriver", "writeRegister") + class GBASIODriver(object): def __init__(self): + super(GBASIODriver, self).__init__() + self._handle = ffi.new_handle(self) self._native = ffi.gc(lib.GBASIOPythonDriverCreate(self._handle), lib.free)@@ -81,8 +87,9 @@
def unload(self): return True - def writeRegister(self, address, value): + def write_register(self, address, value): return value + class GBASIOJOYDriver(GBASIODriver): RESET = lib.JOY_RESET@@ -91,10 +98,11 @@ TRANS = lib.JOY_TRANS
RECV = lib.JOY_RECV def __init__(self): - self._handle = ffi.new_handle(self) + super(GBASIOJOYDriver, self).__init__() + self._native = ffi.gc(lib.GBASIOJOYPythonDriverCreate(self._handle), lib.free) - def sendCommand(self, cmd, data): + def send_command(self, cmd, data): buffer = ffi.new('uint8_t[5]') try: buffer[0] = data[0]@@ -110,6 +118,7 @@ if outlen > 0 and outlen <= 5:
return bytes(buffer[0:outlen]) return None + class GBAMemory(Memory): def __init__(self, core, romSize=lib.SIZE_CART0): super(GBAMemory, self).__init__(core, 0x100000000)@@ -117,7 +126,7 @@
self.bios = Memory(core, lib.SIZE_BIOS, lib.BASE_BIOS) self.wram = Memory(core, lib.SIZE_WORKING_RAM, lib.BASE_WORKING_RAM) self.iwram = Memory(core, lib.SIZE_WORKING_IRAM, lib.BASE_WORKING_IRAM) - self.io = Memory(core, lib.SIZE_IO, lib.BASE_IO) + self.io = Memory(core, lib.SIZE_IO, lib.BASE_IO) # pylint: disable=invalid-name self.palette = Memory(core, lib.SIZE_PALETTE_RAM, lib.BASE_PALETTE_RAM) self.vram = Memory(core, lib.SIZE_VRAM, lib.BASE_VRAM) self.oam = Memory(core, lib.SIZE_OAM, lib.BASE_OAM)@@ -127,6 +136,7 @@ self.cart2 = Memory(core, romSize, lib.BASE_CART2)
self.cart = self.cart0 self.rom = self.cart0 self.sram = Memory(core, lib.SIZE_CART_SRAM, lib.BASE_CART_SRAM) + class GBASprite(Sprite): TILE_BASE = 0x800, 0x400@@ -136,18 +146,19 @@ def __init__(self, obj):
self._a = obj.a self._b = obj.b self._c = obj.c - self.x = self._b & 0x1FF - self.y = self._a & 0xFF + self.x = self._b & 0x1FF # pylint: disable=invalid-name + self.y = self._a & 0xFF # pylint: disable=invalid-name self._shape = self._a >> 14 self._size = self._b >> 14 - self._256Color = bool(self._a & 0x2000) + self._256_color = bool(self._a & 0x2000) self.width, self.height = lib.GBAVideoObjSizes[self._shape * 4 + self._size] self.tile = self._c & 0x3FF - if self._256Color: - self.paletteId = 0 + if self._256_color: + self.palette_id = 0 self.tile >>= 1 else: - self.paletteId = self._c >> 12 + self.palette_id = self._c >> 12 + class GBAObjs: def __init__(self, core):@@ -161,7 +172,7 @@ def __getitem__(self, index):
if index >= len(self): raise IndexError() sprite = GBASprite(self._obj[index]) - tiles = self._core.tiles[3 if sprite._256Color else 2] - map1D = bool(self._core._native.memory.io[0] & 0x40) - sprite.constitute(tiles, 0 if map1D else 0x20) + tiles = self._core.tiles[3 if sprite._256_color else 2] + map_1d = bool(self._core._native.memory.io[0] & 0x40) + sprite.constitute(tiles, 0 if map_1d else 0x20) return sprite
@@ -3,13 +3,14 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi # pylint: disable=no-name-in-module from . import png try: import PIL.Image as PImage except ImportError: pass + class Image: def __init__(self, width, height, stride=0, alpha=False):@@ -24,58 +25,63 @@ if self.stride <= 0:
self.stride = self.width self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height)) - def savePNG(self, f): - p = png.PNG(f, mode=png.MODE_RGBA if self.alpha else png.MODE_RGB) - success = p.writeHeader(self) - success = success and p.writePixels(self) - p.writeClose() + def save_png(self, fileobj): + png_file = png.PNG(fileobj, mode=png.MODE_RGBA if self.alpha else png.MODE_RGB) + success = png_file.write_header(self) + success = success and png_file.write_pixels(self) + png_file.write_close() return success if 'PImage' in globals(): - def toPIL(self): - type = "RGBA" if self.alpha else "RGBX" - return PImage.frombytes(type, (self.width, self.height), ffi.buffer(self.buffer), "raw", - type, self.stride * 4) + def to_pil(self): + colorspace = "RGBA" if self.alpha else "RGBX" + return PImage.frombytes(colorspace, (self.width, self.height), ffi.buffer(self.buffer), "raw", + colorspace, self.stride * 4) + -def u16ToU32(c): - r = c & 0x1F - g = (c >> 5) & 0x1F - b = (c >> 10) & 0x1F - a = (c >> 15) & 1 +def u16_to_u32(color): + # pylint: disable=invalid-name + r = color & 0x1F + g = (color >> 5) & 0x1F + b = (color >> 10) & 0x1F + a = (color >> 15) & 1 abgr = r << 3 abgr |= g << 11 abgr |= b << 19 abgr |= (a * 0xFF) << 24 return abgr -def u32ToU16(c): - r = (c >> 3) & 0x1F - g = (c >> 11) & 0x1F - b = (c >> 19) & 0x1F - a = c >> 31 + +def u32_to_u16(color): + # pylint: disable=invalid-name + r = (color >> 3) & 0x1F + g = (color >> 11) & 0x1F + b = (color >> 19) & 0x1F + a = color >> 31 abgr = r abgr |= g << 5 abgr |= b << 10 abgr |= a << 15 return abgr + if ffi.sizeof("color_t") == 2: - def colorToU16(c): - return c + def color_to_u16(color): + return color - colorToU32 = u16ToU32 + color_to_u32 = u16_to_u32 # pylint: disable=invalid-name - def u16ToColor(c): - return c + def u16_to_color(color): + return color - u32ToColor = u32ToU16 + u32_to_color = u32_to_u16 # pylint: disable=invalid-name else: - def colorToU32(c): - return c + def color_to_u32(color): + return color - colorToU16 = u32ToU16 + color_to_u16 = u32_to_u16 # pylint: disable=invalid-name - def u32ToColor(c): - return c + def u32_to_color(color): + return color - u16ToColor = u16ToU32 + u16_to_color = u16_to_u32 # pylint: disable=invalid-name
@@ -3,17 +3,15 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib -from . import createCallback +from ._pylib import ffi, lib # pylint: disable=no-name-in-module +from . import create_callback -createCallback("mLoggerPy", "log", "_pyLog") +create_callback("mLoggerPy", "log", "_pyLog") -defaultLogger = None -def installDefault(logger): - global defaultLogger - defaultLogger = logger - lib.mLogSetDefaultLogger(logger._native) +def install_default(logger): + Logger.install_default(logger) + class Logger(object): FATAL = lib.mLOG_FATAL@@ -24,16 +22,24 @@ ERROR = lib.mLOG_ERROR
STUB = lib.mLOG_STUB GAME_ERROR = lib.mLOG_GAME_ERROR + _DEFAULT_LOGGER = None + def __init__(self): self._handle = ffi.new_handle(self) self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) @staticmethod - def categoryName(category): + def category_name(category): return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') + @classmethod + def install_default(cls, logger): + cls._DEFAULT_LOGGER = logger + lib.mLogSetDefaultLogger(logger._native) + def log(self, category, level, message): - print("{}: {}".format(self.categoryName(category), message)) + print("{}: {}".format(self.category_name(category), message)) + class NullLogger(Logger): def log(self, category, level, message):
@@ -3,9 +3,11 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi # pylint: disable=no-name-in-module + class LR35902Core: + # pylint: disable=invalid-name def __init__(self, native): self._native = ffi.cast("struct LR35902Core*", native)
@@ -3,7 +3,8 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module + class MemoryView(object): def __init__(self, core, width, size, base=0, sign="u"):@@ -11,11 +12,11 @@ self._core = core
self._width = width self._size = size self._base = base - self._busRead = getattr(self._core, "busRead" + str(width * 8)) - self._busWrite = getattr(self._core, "busWrite" + str(width * 8)) - self._rawRead = getattr(self._core, "rawRead" + str(width * 8)) - self._rawWrite = getattr(self._core, "rawWrite" + str(width * 8)) - self._mask = (1 << (width * 8)) - 1 # Used to force values to fit within range so that negative values work + self._bus_read = getattr(self._core, "busRead" + str(width * 8)) + self._bus_write = getattr(self._core, "busWrite" + str(width * 8)) + self._raw_read = getattr(self._core, "rawRead" + str(width * 8)) + self._raw_write = getattr(self._core, "rawWrite" + str(width * 8)) + self._mask = (1 << (width * 8)) - 1 # Used to force values to fit within range so that negative values work if sign == "u" or sign == "unsigned": self._type = "uint{}_t".format(width * 8) elif sign == "i" or sign == "s" or sign == "signed":@@ -23,7 +24,7 @@ self._type = "int{}_t".format(width * 8)
else: raise ValueError("Invalid sign type: '{}'".format(sign)) - def _addrCheck(self, address): + def _addr_check(self, address): if isinstance(address, slice): start = address.start or 0 stop = self._size - self._width if address.stop is None else address.stop@@ -39,33 +40,32 @@ def __len__(self):
return self._size def __getitem__(self, address): - self._addrCheck(address) + self._addr_check(address) if isinstance(address, slice): start = address.start or 0 stop = self._size - self._width if address.stop is None else address.stop step = address.step or self._width - return [int(ffi.cast(self._type, self._busRead(self._core, self._base + a))) for a in range(start, stop, step)] - else: - return int(ffi.cast(self._type, self._busRead(self._core, self._base + address))) + return [int(ffi.cast(self._type, self._bus_read(self._core, self._base + a))) for a in range(start, stop, step)] + return int(ffi.cast(self._type, self._bus_read(self._core, self._base + address))) def __setitem__(self, address, value): - self._addrCheck(address) + self._addr_check(address) if isinstance(address, slice): start = address.start or 0 stop = self._size - self._width if address.stop is None else address.stop step = address.step or self._width - for a in range(start, stop, step): - self._busWrite(self._core, self._base + a, value[a] & self._mask) + for addr in range(start, stop, step): + self._bus_write(self._core, self._base + addr, value[addr] & self._mask) else: - self._busWrite(self._core, self._base + address, value & self._mask) + self._bus_write(self._core, self._base + address, value & self._mask) - def rawRead(self, address, segment=-1): - self._addrCheck(address) - return int(ffi.cast(self._type, self._rawRead(self._core, self._base + address, segment))) + def raw_read(self, address, segment=-1): + self._addr_check(address) + return int(ffi.cast(self._type, self._raw_read(self._core, self._base + address, segment))) - def rawWrite(self, address, value, segment=-1): - self._addrCheck(address) - self._rawWrite(self._core, self._base + address, segment, value & self._mask) + def raw_write(self, address, value, segment=-1): + self._addr_check(address) + self._raw_write(self._core, self._base + address, segment, value & self._mask) class MemorySearchResult(object):@@ -75,12 +75,13 @@ self.segment = result.segment
self.guessDivisor = result.guessDivisor self.type = result.type - if result.type == Memory.SEARCH_8: - self._memory = memory.u8 - elif result.type == Memory.SEARCH_16: - self._memory = memory.u16 - elif result.type == Memory.SEARCH_32: - self._memory = memory.u32 + if result.type == Memory.SEARCH_INT: + if result.width == 1: + self._memory = memory.u8 + elif result.width == 2: + self._memory = memory.u16 + elif result.width == 4: + self._memory = memory.u32 elif result.type == Memory.SEARCH_STRING: self._memory = memory.u8 else:@@ -123,7 +124,7 @@ self.s16 = MemoryView(core, 2, size, base, "s")
self.s32 = MemoryView(core, 4, size, base, "s") def __len__(self): - return self._size + return self.size def search(self, value, type=SEARCH_GUESS, flags=RW, limit=10000, old_results=[]): results = ffi.new("struct mCoreMemorySearchResults*")@@ -138,11 +139,11 @@ else:
params.valueStr = ffi.new("char[]", str(value).encode("ascii")) for result in old_results: - r = lib.mCoreMemorySearchResultsAppend(results) - r.address = result.address - r.segment = result.segment - r.guessDivisor = result.guessDivisor - r.type = result.type + native_result = lib.mCoreMemorySearchResultsAppend(results) + native_result.address = result.address + native_result.segment = result.segment + native_result.guessDivisor = result.guessDivisor + native_result.type = result.type if old_results: lib.mCoreMemorySearchRepeat(self._core, params, results) else:@@ -154,5 +155,4 @@
def __getitem__(self, address): if isinstance(address, slice): return bytearray(self.u8[address]) - else: - return self.u8[address] + return self.u8[address]
@@ -3,37 +3,41 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from . import vfs MODE_RGB = 0 MODE_RGBA = 1 MODE_INDEX = 2 + class PNG: - def __init__(self, f, mode=MODE_RGB): - self.vf = vfs.open(f) - self.mode = mode + def __init__(self, f, mode=MODE_RGB): + self._vfile = vfs.open(f) + self._png = None + self._info = None + self.mode = mode - def writeHeader(self, image): - self._png = lib.PNGWriteOpen(self.vf.handle) - if self.mode == MODE_RGB: - self._info = lib.PNGWriteHeader(self._png, image.width, image.height) - if self.mode == MODE_RGBA: - self._info = lib.PNGWriteHeaderA(self._png, image.width, image.height) - if self.mode == MODE_INDEX: - self._info = lib.PNGWriteHeader8(self._png, image.width, image.height) - return self._info != ffi.NULL + def write_header(self, image): + self._png = lib.PNGWriteOpen(self._vfile.handle) + if self.mode == MODE_RGB: + self._info = lib.PNGWriteHeader(self._png, image.width, image.height) + if self.mode == MODE_RGBA: + self._info = lib.PNGWriteHeaderA(self._png, image.width, image.height) + if self.mode == MODE_INDEX: + self._info = lib.PNGWriteHeader8(self._png, image.width, image.height) + return self._info != ffi.NULL - def writePixels(self, image): - if self.mode == MODE_RGB: - return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer) - if self.mode == MODE_RGBA: - return lib.PNGWritePixelsA(self._png, image.width, image.height, image.stride, image.buffer) - if self.mode == MODE_INDEX: - return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer) + def write_pixels(self, image): + if self.mode == MODE_RGB: + return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer) + if self.mode == MODE_RGBA: + return lib.PNGWritePixelsA(self._png, image.width, image.height, image.stride, image.buffer) + if self.mode == MODE_INDEX: + return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer) + return False - def writeClose(self): - lib.PNGWriteClose(self._png, self._info) - del self._png - del self._info + def write_close(self): + lib.PNGWriteClose(self._png, self._info) + self._png = None + self._info = None
@@ -3,8 +3,9 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from .core import IRunner, ICoreOwner, Core + class ThreadCoreOwner(ICoreOwner): def __init__(self, thread):@@ -18,13 +19,14 @@ return self.thread._core
def release(self): lib.mCoreThreadContinue(self.thread._native) + class Thread(IRunner): def __init__(self, native=None): if native: self._native = native self._core = Core(native.core) - self._core._wasReset = lib.mCoreThreadHasStarted(self._native) + self._core._was_reset = lib.mCoreThreadHasStarted(self._native) else: self._native = ffi.new("struct mCoreThread*")@@ -34,7 +36,7 @@ raise ValueError
self._core = core self._native.core = core._core lib.mCoreThreadStart(self._native) - self._core._wasReset = lib.mCoreThreadHasStarted(self._native) + self._core._was_reset = lib.mCoreThreadHasStarted(self._native) def end(self): if not lib.mCoreThreadHasStarted(self._native):@@ -48,11 +50,13 @@
def unpause(self): lib.mCoreThreadUnpause(self._native) - def isRunning(self): + @property + def running(self): return bool(lib.mCoreThreadIsActive(self._native)) - def isPaused(self): + @property + def paused(self): return bool(lib.mCoreThreadIsPaused(self._native)) - def useCore(self): + def use_core(self): return ThreadCoreOwner(self)
@@ -3,14 +3,15 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from . import image + class Tile: def __init__(self, data): self.buffer = data - def toImage(self): + def to_image(self): i = image.Image(8, 8) self.composite(i, 0, 0) return i@@ -19,18 +20,21 @@ def composite(self, i, x, y):
for iy in range(8): ffi.memmove(ffi.addressof(i.buffer, x + (iy + y) * i.stride), ffi.addressof(self.buffer, iy * 8), 8 * ffi.sizeof("color_t")) + class CacheSet: def __init__(self, core): self.core = core - self.cache = ffi.gc(ffi.new("struct mCacheSet*"), core._deinitCache) - core._initCache(self.cache) + self.cache = ffi.gc(ffi.new("struct mCacheSet*"), core._deinit_cache) + core._init_cache(self.cache) + class TileView: def __init__(self, cache): self.cache = cache - def getTile(self, tile, palette): + def get_tile(self, tile, palette): return Tile(lib.mTileCacheGetTile(self.cache, tile, palette)) + class MapView: def __init__(self, cache):@@ -53,6 +57,7 @@ lib.mMapCacheCleanRow(self.cache, y >> 3)
row = lib.mMapCacheGetRow(self.cache, y) ffi.memmove(ffi.addressof(i.buffer, i.stride * y), row, self.width * 8 * ffi.sizeof("color_t")) return i + class Sprite(object): def constitute(self, tileView, tilePitch):
@@ -3,139 +3,152 @@ #
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib -import mmap +# pylint: disable=invalid-name,unused-argument +from ._pylib import ffi, lib # pylint: disable=no-name-in-module import os + @ffi.def_extern() def _vfpClose(vf): - vfp = ffi.cast("struct VFilePy*", vf) - ffi.from_handle(vfp.fileobj).close() - return True + vfp = ffi.cast("struct VFilePy*", vf) + ffi.from_handle(vfp.fileobj).close() + return True + @ffi.def_extern() def _vfpSeek(vf, offset, whence): - vfp = ffi.cast("struct VFilePy*", vf) - f = ffi.from_handle(vfp.fileobj) - f.seek(offset, whence) - return f.tell() + vfp = ffi.cast("struct VFilePy*", vf) + f = ffi.from_handle(vfp.fileobj) + f.seek(offset, whence) + return f.tell() + @ffi.def_extern() def _vfpRead(vf, buffer, size): - vfp = ffi.cast("struct VFilePy*", vf) - pybuf = ffi.buffer(buffer, size) - ffi.from_handle(vfp.fileobj).readinto(pybuf) - return size + vfp = ffi.cast("struct VFilePy*", vf) + pybuf = ffi.buffer(buffer, size) + ffi.from_handle(vfp.fileobj).readinto(pybuf) + return size + @ffi.def_extern() def _vfpWrite(vf, buffer, size): - vfp = ffi.cast("struct VFilePy*", vf) - pybuf = ffi.buffer(buffer, size) - ffi.from_handle(vfp.fileobj).write(pybuf) - return size + vfp = ffi.cast("struct VFilePy*", vf) + pybuf = ffi.buffer(buffer, size) + ffi.from_handle(vfp.fileobj).write(pybuf) + return size + @ffi.def_extern() def _vfpMap(vf, size, flags): - pass + pass + @ffi.def_extern() def _vfpUnmap(vf, memory, size): - pass + pass + @ffi.def_extern() def _vfpTruncate(vf, size): - vfp = ffi.cast("struct VFilePy*", vf) - ffi.from_handle(vfp.fileobj).truncate(size) + vfp = ffi.cast("struct VFilePy*", vf) + ffi.from_handle(vfp.fileobj).truncate(size) + @ffi.def_extern() def _vfpSize(vf): - vfp = ffi.cast("struct VFilePy*", vf) - f = ffi.from_handle(vfp.fileobj) - pos = f.tell() - f.seek(0, os.SEEK_END) - size = f.tell() - f.seek(pos, os.SEEK_SET) - return size + vfp = ffi.cast("struct VFilePy*", vf) + f = ffi.from_handle(vfp.fileobj) + pos = f.tell() + f.seek(0, os.SEEK_END) + size = f.tell() + f.seek(pos, os.SEEK_SET) + return size + @ffi.def_extern() def _vfpSync(vf, buffer, size): - vfp = ffi.cast("struct VFilePy*", vf) - f = ffi.from_handle(vfp.fileobj) - if buffer and size: - pos = f.tell() - f.seek(0, os.SEEK_SET) - _vfpWrite(vf, buffer, size) - f.seek(pos, os.SEEK_SET) - f.flush() - os.fsync() - return True + vfp = ffi.cast("struct VFilePy*", vf) + f = ffi.from_handle(vfp.fileobj) + if buffer and size: + pos = f.tell() + f.seek(0, os.SEEK_SET) + _vfpWrite(vf, buffer, size) + f.seek(pos, os.SEEK_SET) + f.flush() + os.fsync() + return True + -def open(f): - handle = ffi.new_handle(f) - vf = VFile(lib.VFileFromPython(handle)) - # Prevent garbage collection - vf._fileobj = f - vf._handle = handle - return vf +def open(f): # pylint: disable=redefined-builtin + handle = ffi.new_handle(f) + vf = VFile(lib.VFileFromPython(handle), _no_gc=(f, handle)) + return vf + + +def open_path(path, mode="r"): + flags = 0 + if mode.startswith("r"): + flags |= os.O_RDONLY + elif mode.startswith("w"): + flags |= os.O_WRONLY | os.O_CREAT | os.O_TRUNC + elif mode.startswith("a"): + flags |= os.O_WRONLY | os.O_CREAT | os.O_APPEND + else: + return None -def openPath(path, mode="r"): - flags = 0 - if mode.startswith("r"): - flags |= os.O_RDONLY - elif mode.startswith("w"): - flags |= os.O_WRONLY | os.O_CREAT | os.O_TRUNC - elif mode.startswith("a"): - flags |= os.O_WRONLY | os.O_CREAT | os.O_APPEND - else: - return None + if "+" in mode[1:]: + flags |= os.O_RDWR + if "x" in mode[1:]: + flags |= os.O_EXCL - if "+" in mode[1:]: - flags |= os.O_RDWR - if "x" in mode[1:]: - flags |= os.O_EXCL + vf = lib.VFileOpen(path.encode("UTF-8"), flags) + if vf == ffi.NULL: + return None + return VFile(vf) - vf = lib.VFileOpen(path.encode("UTF-8"), flags); - if vf == ffi.NULL: - return None - return VFile(vf) class VFile: - def __init__(self, vf): - self.handle = vf + def __init__(self, vf, _no_gc=None): + self.handle = vf + self._no_gc = _no_gc - def close(self): - return bool(self.handle.close(self.handle)) + def __del__(self): + self.close() + + def close(self): + return bool(self.handle.close(self.handle)) - def seek(self, offset, whence): - return self.handle.seek(self.handle, offset, whence) + def seek(self, offset, whence): + return self.handle.seek(self.handle, offset, whence) - def read(self, buffer, size): - return self.handle.read(self.handle, buffer, size) + def read(self, buffer, size): + return self.handle.read(self.handle, buffer, size) - def readAll(self, size=0): - if not size: - size = self.size() - buffer = ffi.new("char[%i]" % size) - size = self.handle.read(self.handle, buffer, size) - return ffi.unpack(buffer, size) + def read_all(self, size=0): + if not size: + size = self.size() + buffer = ffi.new("char[%i]" % size) + size = self.handle.read(self.handle, buffer, size) + return ffi.unpack(buffer, size) - def readline(self, buffer, size): - return self.handle.readline(self.handle, buffer, size) + def readline(self, buffer, size): + return self.handle.readline(self.handle, buffer, size) - def write(self, buffer, size): - return self.handle.write(self.handle, buffer, size) + def write(self, buffer, size): + return self.handle.write(self.handle, buffer, size) - def map(self, size, flags): - return self.handle.map(self.handle, size, flags) + def map(self, size, flags): + return self.handle.map(self.handle, size, flags) - def unmap(self, memory, size): - self.handle.unmap(self.handle, memory, size) + def unmap(self, memory, size): + self.handle.unmap(self.handle, memory, size) - def truncate(self, size): - self.handle.truncate(self.handle, size) + def truncate(self, size): + self.handle.truncate(self.handle, size) - def size(self): - return self.handle.size(self.handle) + def size(self): + return self.handle.size(self.handle) - def sync(self, buffer, size): - return self.handle.sync(self.handle, buffer, size) + def sync(self, buffer, size): + return self.handle.sync(self.handle, buffer, size)
@@ -1,2 +1,6 @@
[aliases] test=pytest + +[pycodestyle] +exclude = .eggs +ignore = E501,E741,E743
@@ -0,0 +1,38 @@
+from setuptools import setup +import re +import os +import os.path +import sys +import subprocess + + +def get_version_component(piece): + return subprocess.check_output(['cmake', '-DPRINT_STRING={}'.format(piece), '-P', '../../../version.cmake']).decode('utf-8').strip() + + +version = '{}.{}.{}'.format(*(get_version_component(p) for p in ('LIB_VERSION_MAJOR', 'LIB_VERSION_MINOR', 'LIB_VERSION_PATCH'))) +if not get_version_component('GIT_TAG'): + version += '.{}+g{}'.format(*(get_version_component(p) for p in ('GIT_REV', 'GIT_COMMIT_SHORT'))) + +setup( + name="mgba", + version=version, + author="Jeffrey Pfau", + author_email="jeffrey@endrift.com", + url="http://github.com/mgba-emu/mgba/", + packages=["mgba"], + setup_requires=['cffi>=1.6', 'pytest-runner'], + install_requires=['cffi>=1.6', 'cached-property'], + extras_require={'pil': ['Pillow>=2.3'], 'cinema': ['pyyaml', 'pytest']}, + tests_require=['pytest'], + cffi_modules=["_builder.py:ffi"], + license="MPL 2.0", + classifiers=[ + "Programming Language :: C", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Topic :: Games/Entertainment", + "Topic :: System :: Emulators" + ] +)
@@ -4,7 +4,7 @@ import mgba.log
import os.path import yaml -mgba.log.installDefault(mgba.log.NullLogger()) +mgba.log.install_default(mgba.log.NullLogger()) def flatten(d): l = []@@ -18,7 +18,7 @@ return l
def pytest_generate_tests(metafunc): if 'vtest' in metafunc.fixturenames: - tests = cinema.test.gatherTests(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'cinema')) + tests = cinema.test.gather_tests(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'cinema')) testList = flatten(tests) params = [] for test in testList:@@ -34,9 +34,9 @@ def vtest(request):
return request.param def test_video(vtest, pytestconfig): - vtest.setUp() + vtest.setup() if pytestconfig.getoption('--rebaseline'): - vtest.generateBaseline() + vtest.generate_baseline() else: try: vtest.test()
@@ -4,26 +4,30 @@
import mgba.vfs as vfs from mgba._pylib import ffi + def test_vfs_open(): with open(__file__) as f: vf = vfs.open(f) assert vf assert vf.close() -def test_vfs_openPath(): - vf = vfs.openPath(__file__) + +def test_vfs_open_path(): + vf = vfs.open_path(__file__) assert vf assert vf.close() + def test_vfs_read(): - vf = vfs.openPath(__file__) + vf = vfs.open_path(__file__) buffer = ffi.new('char[13]') assert vf.read(buffer, 13) == 13 assert ffi.string(buffer) == b'import pytest' vf.close() + def test_vfs_readline(): - vf = vfs.openPath(__file__) + vf = vfs.open_path(__file__) buffer = ffi.new('char[16]') linelen = vf.readline(buffer, 16) assert linelen in (14, 15)@@ -33,16 +37,18 @@ elif linelen == 15:
assert ffi.string(buffer) == b'import pytest\r\n' vf.close() -def test_vfs_readAllSize(): - vf = vfs.openPath(__file__) - buffer = vf.readAll() + +def test_vfs_read_all_size(): + vf = vfs.open_path(__file__) + buffer = vf.read_all() assert buffer assert len(buffer) assert len(buffer) == vf.size() vf.close() + def test_vfs_seek(): - vf = vfs.openPath(__file__) + vf = vfs.open_path(__file__) assert vf.seek(0, os.SEEK_SET) == 0 assert vf.seek(1, os.SEEK_SET) == 1 assert vf.seek(1, os.SEEK_CUR) == 2@@ -52,6 +58,7 @@ assert vf.seek(0, os.SEEK_END) == vf.size()
assert vf.seek(-1, os.SEEK_END) == vf.size() -1 vf.close() -def test_vfs_openPath_invalid(): - vf = vfs.openPath('.invalid') + +def test_vfs_open_path_invalid(): + vf = vfs.open_path('.invalid') assert not vf
@@ -6,9 +6,7 @@ set(PLATFORM_SRC)
set(QT_STATIC OFF) if(BUILD_SDL) - if(NOT SDL_FOUND AND NOT SDL2_FOUND) - find_package(SDL 1.2 REQUIRED) - endif() + add_definitions(-DBUILD_SDL) if(SDL2_FOUND) link_directories(${SDL2_LIBDIR}) endif()@@ -33,9 +31,9 @@ if(NOT BUILD_GL AND NOT BUILD_GLES2)
message(WARNING "OpenGL is recommended to build the Qt port") endif() +set(FOUND_QT ${Qt5Widgets_FOUND} PARENT_SCOPE) if(NOT Qt5Widgets_FOUND) message(WARNING "Cannot find Qt modules") - set(BUILD_QT OFF PARENT_SCOPE) return() endif()
@@ -204,12 +204,12 @@ endVideoLog();
stop(); disconnect(); + mCoreThreadJoin(&m_threadContext); + if (m_cacheSet) { mCacheSetDeinit(m_cacheSet.get()); m_cacheSet.reset(); } - - mCoreThreadJoin(&m_threadContext); mCoreConfigDeinit(&m_threadContext.core->config); m_threadContext.core->deinit(m_threadContext.core);@@ -360,6 +360,7 @@ }
} void CoreController::stop() { + setSync(false); #ifdef USE_DEBUGGERS setDebugger(nullptr); #endif
@@ -10,6 +10,7 @@ #include "GamepadAxisEvent.h"
#include "GamepadButtonEvent.h" #include "VFileDevice.h" +#include <QAction> #include <QDateTime> #include <QKeyEvent> #include <QPainter>
@@ -160,10 +160,12 @@ setCentralWidget(m_screenWidget);
connect(this, &Window::shutdown, m_logView, &QWidget::hide); connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS); + connect(&m_frameTimer, &QTimer::timeout, this, &Window::delimitFrames); connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck); m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); + m_frameTimer.setInterval(FRAME_LIST_INTERVAL); m_focusCheck.setInterval(200); setupMenu(menuBar());@@ -314,6 +316,7 @@ #ifdef M_CORE_GB
QStringList gbFormats{ "*.gb", "*.gbc", + "*.sgb", #if defined(USE_LIBZIP) || defined(USE_ZLIB) "*.zip", #endif@@ -726,8 +729,6 @@ m_controller->stop();
return; } #endif - multiplayerChanged(); - updateTitle(); QSize size = m_controller->screenDimensions(); m_screenWidget->setDimensions(size.width(), size.height()); m_config->updateOption("lockIntegerScaling");@@ -744,10 +745,16 @@ if (isFullScreen()) {
menuBar()->hide(); } #endif + m_display->startDrawing(m_controller); + + reloadAudioDriver(); + multiplayerChanged(); + updateTitle(); m_hitUnimplementedBiosCall = false; if (m_config->getOption("showFps", "1").toInt()) { m_fpsTimer.start(); + m_frameTimer.start(); } m_focusCheck.start(); if (m_display->underMouse()) {@@ -785,12 +792,10 @@ });
m_audioChannels->addAction(action); } } - m_display->startDrawing(m_controller); - - reloadAudioDriver(); } void Window::gameStopped() { + m_controller.reset(); for (QPair<QAction*, int> action : m_platformActions) { action.first->setDisabled(false); }@@ -816,6 +821,7 @@ m_videoLayers->clear();
m_audioChannels->clear(); m_fpsTimer.stop(); + m_frameTimer.stop(); m_focusCheck.stop(); emit paused(false);@@ -944,19 +950,27 @@ dialog->show();
} void Window::recordFrame() { - m_frameList.append(QDateTime::currentDateTime()); - while (m_frameList.count() > FRAME_LIST_SIZE) { - m_frameList.removeFirst(); + if (m_frameList.isEmpty()) { + m_frameList.append(1); + } 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() { if (m_frameList.isEmpty()) { updateTitle(); return; } - qint64 interval = m_frameList.first().msecsTo(m_frameList.last()); - float fps = (m_frameList.count() - 1) * 10000.f / interval; + float fps = m_frameCounter * 10000.f / (FRAME_LIST_INTERVAL * (m_frameList.size() - 1)); fps = round(fps) / 10.f; updateTitle(fps); }@@ -1679,9 +1693,11 @@ ConfigOption* showFps = m_config->addOption("showFps");
showFps->connect([this](const QVariant& value) { if (!value.toInt()) { m_fpsTimer.stop(); + m_frameTimer.stop(); updateTitle(); } else if (m_controller) { m_fpsTimer.start(); + m_frameTimer.start(); } }, this);@@ -1792,15 +1808,17 @@ void Window::setController(CoreController* controller, const QString& fname) {
if (!controller) { return; } - if (!fname.isEmpty()) { - setWindowFilePath(fname); - appendMRU(fname); - } if (m_controller) { - m_controller->disconnect(this); m_controller->stop(); - m_controller.reset(); + QTimer::singleShot(0, this, [this, controller, fname]() { + setController(controller, fname); + }); + return; + } + if (!fname.isEmpty()) { + setWindowFilePath(fname); + appendMRU(fname); } m_controller = std::shared_ptr<CoreController>(controller);
@@ -5,6 +5,7 @@ * 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/. */ #pragma once +#include <QAction> #include <QDateTime> #include <QList> #include <QMainWindow>@@ -130,6 +131,7 @@ void tryMakePortable();
void mustRestart(); void recordFrame(); + void delimitFrames(); void showFPS(); void focusCheck();@@ -137,7 +139,8 @@ void updateFrame();
private: 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 openStateWindow(LoadSave);@@ -183,8 +186,10 @@ WindowBackground* m_screenWidget;
QPixmap m_logo{":/res/medusa-bg.jpg"}; ConfigController* m_config; InputController m_inputController; - QList<QDateTime> m_frameList; + QList<int> m_frameList; + int m_frameCounter = 0; QTimer m_fpsTimer; + QTimer m_frameTimer; QList<QString> m_mruFiles; QMenu* m_mruMenu = nullptr; QMenu* m_videoLayers;
@@ -37,8 +37,10 @@
using namespace QGBA; 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(); +#endif #endif ConfigController configController;
@@ -13,13 +13,15 @@ endif()
if(SDL_VERSION EQUAL "1.2" OR NOT SDL2_FOUND) find_package(SDL 1.2) - set(SDL_VERSION "1.2" PARENT_SCOPE) - set(SDL_VERSION_DEBIAN "1.2debian") - set(USE_PIXMAN ON) + if(SDL_FOUND) + set(SDL_VERSION "1.2" PARENT_SCOPE) + set(SDL_VERSION_DEBIAN "1.2debian") + set(USE_PIXMAN ON) + endif() endif() if (NOT SDL2_FOUND AND NOT SDL_FOUND) - set(BUILD_SDL OFF PARENT_SCOPE) + set(SDL_FOUND OFF PARENT_SCOPE) return() endif()
@@ -47,7 +47,7 @@ set(GIT_BRANCH "(unknown)")
endif() if(DEFINED PRINT_STRING) - message("${${PRINT_STRING}}") + execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${${PRINT_STRING}}") elseif(NOT VERSION_STRING_CACHE OR NOT VERSION_STRING STREQUAL VERSION_STRING_CACHE) set(VERSION_STRING_CACHE ${VERSION_STRING} CACHE STRING "" FORCE)