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

View File

@ -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 @@ if(BUILD_EXAMPLE)
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 @@ if(BUILD_QT)
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)

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:
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` ..

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:
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' ..

View File

@ -136,12 +136,13 @@ typedef intptr_t ssize_t;
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 @@ typedef intptr_t ssize_t;
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)

View File

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

View File

@ -15,7 +15,7 @@
#cmakedefine BUILD_GLES2
#endif
// COLOR flags
// Miscellaneous flags
#ifndef COLOR_16_BIT
#cmakedefine COLOR_16_BIT
@ -25,6 +25,14 @@
#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 @@
#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
@ -51,6 +63,10 @@
#cmakedefine USE_EDITLINE
#endif
#ifndef USE_ELF
#cmakedefine USE_ELF
#endif
#ifndef USE_EPOXY
#cmakedefine USE_EPOXY
#endif
@ -100,5 +116,3 @@
#endif
#endif
#endif

View File

@ -5,6 +5,8 @@
* 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 @@ void mCoreSyncSetVideoSync(struct mCoreSync* sync, bool 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) {
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) {

View File

@ -5,6 +5,7 @@
* 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 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
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);

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);
}
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);

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);
}
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);

View File

@ -644,17 +644,17 @@ void GBProcessEvents(struct LR35902Core* cpu) {
} 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) {

View File

@ -104,6 +104,8 @@ static const uint8_t _registerMask[] = {
[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 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t 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 @@ 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));
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 @@ 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));
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 @@ static uint8_t _readKeys(struct GB* gb) {
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 @@ static uint8_t _readKeys(struct GB* gb) {
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 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
}
void GBTestKeypadIRQ(struct GB* gb) {
if (_readKeys(gb)) {
gb->memory.io[REG_IF] |= (1 << GB_IRQ_KEYPAD);
GBUpdateIRQs(gb);
}
_readKeys(gb);
}
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)];
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 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) {
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 @@ uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) {
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 {

View File

@ -12,7 +12,12 @@
#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 } }
};

View File

@ -147,7 +147,7 @@ void GBVideoReset(struct GBVideo* video) {
}
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 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) {
}
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;

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);
}
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);

View File

@ -809,7 +809,7 @@ static void _unBitPack(struct GBA* gba) {
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;

View File

@ -264,11 +264,6 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
} 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 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
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!");

View File

@ -293,7 +293,7 @@ static const int _isWSpecialRegister[REG_MAX >> 1] = {
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,

View File

@ -163,6 +163,9 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
value &= ~0x0080;
}
break;
case SIO_MULTI:
value |= 0xC;
break;
default:
// TODO
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
* 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_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];
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) {
*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 @@ void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timi
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) {

View File

@ -80,16 +80,18 @@ void GBAVideoInit(struct GBAVideo* video) {
}
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 @@ void GBAVideoReset(struct GBAVideo* video) {
}
void GBAVideoDeinit(struct GBAVideo* video) {
GBAVideoAssociateRenderer(video, &dummyRenderer);
video->renderer->deinit(video->renderer);
mappedMemoryFree(video->vram, SIZE_VRAM);
}

View File

@ -99,6 +99,7 @@ struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) {
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);
}

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)
if(NOT GIT_TAG)
if(GIT_BRANCH STREQUAL "master" OR NOT GIT_BRANCH)
set(PYLIB_VERSION -b .dev${GIT_REV}+g${GIT_COMMIT_SHORT})
else()
set(PYLIB_VERSION -b .dev${GIT_REV}+${GIT_BRANCH}.g${GIT_COMMIT_SHORT})
endif()
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lib.c
COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/lib.c
@ -35,15 +26,29 @@ set_target_properties(${BINARY_NAME}-pylib PROPERTIES INCLUDE_DIRECTORIES "${CMA
set_target_properties(${BINARY_NAME}-pylib PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
add_custom_target(${BINARY_NAME}-py ALL
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py egg_info -e ${CMAKE_CURRENT_BINARY_DIR} ${PYLIB_VERSION}
COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build -b ${CMAKE_CURRENT_BINARY_DIR}
COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py build -b ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${BINARY_NAME}
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/setup.py
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/setup.py
DEPENDS ${PYTHON_HEADERS}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py
DEPENDS ${BINARY_NAME}-pylib)
add_custom_target(${BINARY_NAME}-py-install
COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py install -b ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${BINARY_NAME}-py)
add_custom_target(${BINARY_NAME}-py-develop
COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py develop -b ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${BINARY_NAME}-py)
add_custom_target(${BINARY_NAME}-py-bdist
COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py bdist_wheel -b ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${BINARY_NAME}-py)
file(GLOB BASE_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/test_*.py)
file(GLOB SUBTESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/*/test_*.py)
foreach(TEST IN LISTS BASE_TESTS SUBTESTS)
@ -56,6 +61,8 @@ foreach(TEST IN LISTS BASE_TESTS SUBTESTS)
endif()
string(REGEX REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/(tests/.*/)?test_" "" TEST_NAME "${TEST}")
string(REPLACE ".py" "" TEST_NAME "${TEST_NAME}")
add_test(python-${TEST_NAME} ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} pytest --extras --addopts ${TEST})
set_tests_properties(python-${TEST_NAME} PROPERTIES ENVIRONMENT "${PATH}=${CMAKE_CURRENT_BINARY_DIR}/..")
add_test(NAME python-${TEST_NAME}
COMMAND ${PYTHON_EXECUTABLE} setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} pytest --extras --addopts ${TEST}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_tests_properties(python-${TEST_NAME} PROPERTIES ENVIRONMENT "${PATH}=${CMAKE_CURRENT_BINARY_DIR}/..;BINDIR=${CMAKE_CURRENT_BINARY_DIR}/..;LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/..;CPPFLAGS=${INCLUDE_FLAGS}")
endforeach()

View File

@ -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 @@ ffi.embedding_init_code("""
from mgba.vfs import VFile
vf = VFile(vf)
name = ffi.string(name)
source = vf.readAll().decode('utf-8')
source = vf.read_all().decode('utf-8')
try:
code = compile(source, name, 'exec')
pendingCode.append(code)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,26 +3,27 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from ._pylib import ffi, lib
from ._pylib import ffi, lib # pylint: disable=no-name-in-module
from .core import IRunner, ICoreOwner, Core
import io
import sys
class DebuggerCoreOwner(ICoreOwner):
def __init__(self, debugger):
self.debugger = debugger
self.wasPaused = False
self.was_paused = False
def claim(self):
if self.debugger.isRunning():
self.wasPaused = True
self.was_paused = True
self.debugger.pause()
return self.debugger._core
def release(self):
if self.wasPaused:
if self.was_paused:
self.debugger.unpause()
class NativeDebugger(IRunner):
WATCHPOINT_WRITE = lib.WATCHPOINT_WRITE
WATCHPOINT_READ = lib.WATCHPOINT_READ
@ -49,37 +50,40 @@ class NativeDebugger(IRunner):
def unpause(self):
self._native.state = lib.DEBUGGER_RUNNING
def isRunning(self):
@property
def running(self):
return self._native.state == lib.DEBUGGER_RUNNING
def isPaused(self):
@property
def paused(self):
return self._native.state in (lib.DEBUGGER_PAUSED, lib.DEBUGGER_CUSTOM)
def useCore(self):
def use_core(self):
return DebuggerCoreOwner(self)
def setBreakpoint(self, address):
def set_breakpoint(self, address):
if not self._native.platform.setBreakpoint:
raise RuntimeError("Platform does not support breakpoints")
self._native.platform.setBreakpoint(self._native.platform, address)
def clearBreakpoint(self, address):
def clear_breakpoint(self, address):
if not self._native.platform.setBreakpoint:
raise RuntimeError("Platform does not support breakpoints")
self._native.platform.clearBreakpoint(self._native.platform, address)
def setWatchpoint(self, address):
def set_watchpoint(self, address):
if not self._native.platform.setWatchpoint:
raise RuntimeError("Platform does not support watchpoints")
self._native.platform.setWatchpoint(self._native.platform, address)
def clearWatchpoint(self, address):
def clear_watchpoint(self, address):
if not self._native.platform.clearWatchpoint:
raise RuntimeError("Platform does not support watchpoints")
self._native.platform.clearWatchpoint(self._native.platform, address)
def addCallback(self, cb):
self._cbs.append(cb)
def add_callback(self, callback):
self._cbs.append(callback)
class CLIBackend(object):
def __init__(self, backend):
@ -88,6 +92,7 @@ class CLIBackend(object):
def write(self, string):
self.backend.printf(string)
class CLIDebugger(NativeDebugger):
def __init__(self, native):
super(CLIDebugger, self).__init__(native)
@ -97,5 +102,5 @@ class CLIDebugger(NativeDebugger):
message = message.format(*args, **kwargs)
self._cli.backend.printf(ffi.new("char []", b"%s"), ffi.new("char []", message.encode('utf-8')))
def installPrint(self):
def install_print(self):
sys.stdout = CLIBackend(self)

View File

@ -8,6 +8,7 @@ try:
except ImportError:
pass
def search(core):
crc32 = None
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
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from ._pylib import ffi, lib
from ._pylib import ffi, lib # pylint: disable=no-name-in-module
from .lr35902 import LR35902Core
from .core import Core, needsReset
from .core import Core, needs_reset
from .memory import Memory
from .tile import Sprite
from . import createCallback
from . import create_callback
class GB(Core):
KEY_A = lib.GBA_KEY_A
@ -25,31 +26,34 @@ class GB(Core):
self._native = ffi.cast("struct GB*", native.board)
self.sprites = GBObjs(self)
self.cpu = LR35902Core(self._core.cpu)
self.memory = None
@needsReset
def _initCache(self, cache):
@needs_reset
def _init_cache(self, cache):
lib.GBVideoCacheInit(cache)
lib.GBVideoCacheAssociate(cache, ffi.addressof(self._native.video))
def _deinitCache(self, cache):
def _deinit_cache(self, cache):
lib.mCacheSetDeinit(cache)
if self._wasReset:
if self._was_reset:
self._native.video.renderer.cache = ffi.NULL
def _load(self):
super(GB, self)._load()
self.memory = GBMemory(self._core)
def attachSIO(self, link):
def attach_sio(self, link):
lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native)
def __del__(self):
lib.GBSIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL)
createCallback("GBSIOPythonDriver", "init")
createCallback("GBSIOPythonDriver", "deinit")
createCallback("GBSIOPythonDriver", "writeSB")
createCallback("GBSIOPythonDriver", "writeSC")
create_callback("GBSIOPythonDriver", "init")
create_callback("GBSIOPythonDriver", "deinit")
create_callback("GBSIOPythonDriver", "writeSB")
create_callback("GBSIOPythonDriver", "writeSC")
class GBSIODriver(object):
def __init__(self):
@ -62,53 +66,55 @@ class GBSIODriver(object):
def deinit(self):
pass
def writeSB(self, value):
def write_sb(self, value):
pass
def writeSC(self, value):
def write_sc(self, value):
return value
class GBSIOSimpleDriver(GBSIODriver):
def __init__(self, period=0x100):
super(GBSIOSimpleDriver, self).__init__()
self.rx = 0x00
self.rx = 0x00 # pylint: disable=invalid-name
self._period = period
def init(self):
self._native.p.period = self._period
return True
def writeSB(self, value):
self.rx = value
def write_sb(self, value):
self.rx = value # pylint: disable=invalid-name
def writeSC(self, value):
def write_sc(self, value):
self._native.p.period = self._period
if value & 0x80:
lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event))
lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), self._native.p.period)
return value
def isReady(self):
def is_ready(self):
return not self._native.p.remainingBits
@property
def tx(self):
self._native.p.pendingSB
def tx(self): # pylint: disable=invalid-name
return self._native.p.pendingSB
@property
def period(self):
return self._native.p.period
@tx.setter
def tx(self, newTx):
def tx(self, newTx): # pylint: disable=invalid-name
self._native.p.pendingSB = newTx
self._native.p.remainingBits = 8
@period.setter
def period(self, newPeriod):
self._period = newPeriod
def period(self, new_period):
self._period = new_period
if self._native.p:
self._native.p.period = newPeriod
self._native.p.period = new_period
class GBMemory(Memory):
def __init__(self, core):
@ -119,15 +125,16 @@ class GBMemory(Memory):
self.sram = Memory(core, lib.GB_SIZE_EXTERNAL_RAM, lib.GB_REGION_EXTERNAL_RAM)
self.iwram = Memory(core, lib.GB_SIZE_WORKING_RAM_BANK0, lib.GB_BASE_WORKING_RAM_BANK0)
self.oam = Memory(core, lib.GB_SIZE_OAM, lib.GB_BASE_OAM)
self.io = Memory(core, lib.GB_SIZE_IO, lib.GB_BASE_IO)
self.io = Memory(core, lib.GB_SIZE_IO, lib.GB_BASE_IO) # pylint: disable=invalid-name
self.hram = Memory(core, lib.GB_SIZE_HRAM, lib.GB_BASE_HRAM)
class GBSprite(Sprite):
PALETTE_BASE = 8,
PALETTE_BASE = (8,)
def __init__(self, obj, core):
self.x = obj.x
self.y = obj.y
self.x = obj.x # pylint: disable=invalid-name
self.y = obj.y # pylint: disable=invalid-name
self.tile = obj.tile
self._attr = obj.attr
self.width = 8
@ -136,10 +143,10 @@ class GBSprite(Sprite):
if core._native.model >= lib.GB_MODEL_CGB:
if self._attr & 8:
self.tile += 512
self.paletteId = self._attr & 7
self.palette_id = self._attr & 7
else:
self.paletteId = (self._attr >> 4) & 1
self.paletteId += 8
self.palette_id = (self._attr >> 4) & 1
self.palette_id += 8
class GBObjs:

View File

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

View File

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

View File

@ -3,17 +3,15 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from ._pylib import ffi, lib
from . import createCallback
from ._pylib import ffi, lib # pylint: disable=no-name-in-module
from . import create_callback
createCallback("mLoggerPy", "log", "_pyLog")
create_callback("mLoggerPy", "log", "_pyLog")
defaultLogger = None
def installDefault(logger):
global defaultLogger
defaultLogger = logger
lib.mLogSetDefaultLogger(logger._native)
def install_default(logger):
Logger.install_default(logger)
class Logger(object):
FATAL = lib.mLOG_FATAL
@ -24,16 +22,24 @@ class Logger(object):
STUB = lib.mLOG_STUB
GAME_ERROR = lib.mLOG_GAME_ERROR
_DEFAULT_LOGGER = None
def __init__(self):
self._handle = ffi.new_handle(self)
self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free)
@staticmethod
def categoryName(category):
def category_name(category):
return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8')
@classmethod
def install_default(cls, logger):
cls._DEFAULT_LOGGER = logger
lib.mLogSetDefaultLogger(logger._native)
def log(self, category, level, message):
print("{}: {}".format(self.categoryName(category), message))
print("{}: {}".format(self.category_name(category), message))
class NullLogger(Logger):
def log(self, category, level, message):

View File

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

View File

@ -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 @@ class MemoryView(object):
self._width = width
self._size = size
self._base = base
self._busRead = getattr(self._core, "busRead" + str(width * 8))
self._busWrite = getattr(self._core, "busWrite" + str(width * 8))
self._rawRead = getattr(self._core, "rawRead" + str(width * 8))
self._rawWrite = getattr(self._core, "rawWrite" + str(width * 8))
self._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 @@ class MemoryView(object):
else:
raise ValueError("Invalid sign type: '{}'".format(sign))
def _addrCheck(self, address):
def _addr_check(self, address):
if isinstance(address, slice):
start = address.start or 0
stop = self._size - self._width if address.stop is None else address.stop
@ -39,33 +40,32 @@ class MemoryView(object):
return self._size
def __getitem__(self, address):
self._addrCheck(address)
self._addr_check(address)
if isinstance(address, slice):
start = address.start or 0
stop = self._size - self._width if address.stop is None else address.stop
step = address.step or self._width
return [int(ffi.cast(self._type, self._busRead(self._core, self._base + a))) for a in range(start, stop, step)]
else:
return int(ffi.cast(self._type, self._busRead(self._core, self._base + address)))
return [int(ffi.cast(self._type, self._bus_read(self._core, self._base + a))) for a in range(start, stop, step)]
return int(ffi.cast(self._type, self._bus_read(self._core, self._base + address)))
def __setitem__(self, address, value):
self._addrCheck(address)
self._addr_check(address)
if isinstance(address, slice):
start = address.start or 0
stop = self._size - self._width if address.stop is None else address.stop
step = address.step or self._width
for a in range(start, stop, step):
self._busWrite(self._core, self._base + a, value[a] & self._mask)
for addr in range(start, stop, step):
self._bus_write(self._core, self._base + addr, value[addr] & self._mask)
else:
self._busWrite(self._core, self._base + address, value & self._mask)
self._bus_write(self._core, self._base + address, value & self._mask)
def rawRead(self, address, segment=-1):
self._addrCheck(address)
return int(ffi.cast(self._type, self._rawRead(self._core, self._base + address, segment)))
def raw_read(self, address, segment=-1):
self._addr_check(address)
return int(ffi.cast(self._type, self._raw_read(self._core, self._base + address, segment)))
def rawWrite(self, address, value, segment=-1):
self._addrCheck(address)
self._rawWrite(self._core, self._base + address, segment, value & self._mask)
def raw_write(self, address, value, segment=-1):
self._addr_check(address)
self._raw_write(self._core, self._base + address, segment, value & self._mask)
class MemorySearchResult(object):
@ -75,12 +75,13 @@ class MemorySearchResult(object):
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 @@ class Memory(object):
self.s32 = MemoryView(core, 4, size, base, "s")
def __len__(self):
return self._size
return self.size
def search(self, value, type=SEARCH_GUESS, flags=RW, limit=10000, old_results=[]):
results = ffi.new("struct mCoreMemorySearchResults*")
@ -138,11 +139,11 @@ class Memory(object):
params.valueStr = ffi.new("char[]", str(value).encode("ascii"))
for result in old_results:
r = lib.mCoreMemorySearchResultsAppend(results)
r.address = result.address
r.segment = result.segment
r.guessDivisor = result.guessDivisor
r.type = result.type
native_result = lib.mCoreMemorySearchResultsAppend(results)
native_result.address = result.address
native_result.segment = result.segment
native_result.guessDivisor = result.guessDivisor
native_result.type = result.type
if old_results:
lib.mCoreMemorySearchRepeat(self._core, params, results)
else:
@ -154,5 +155,4 @@ class Memory(object):
def __getitem__(self, address):
if isinstance(address, slice):
return bytearray(self.u8[address])
else:
return self.u8[address]
return self.u8[address]

View File

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

View File

@ -3,9 +3,10 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from ._pylib import ffi, lib
from ._pylib import ffi, lib # pylint: disable=no-name-in-module
from .core import IRunner, ICoreOwner, Core
class ThreadCoreOwner(ICoreOwner):
def __init__(self, thread):
self.thread = thread
@ -19,12 +20,13 @@ class ThreadCoreOwner(ICoreOwner):
def release(self):
lib.mCoreThreadContinue(self.thread._native)
class Thread(IRunner):
def __init__(self, native=None):
if native:
self._native = native
self._core = Core(native.core)
self._core._wasReset = lib.mCoreThreadHasStarted(self._native)
self._core._was_reset = lib.mCoreThreadHasStarted(self._native)
else:
self._native = ffi.new("struct mCoreThread*")
@ -34,7 +36,7 @@ class Thread(IRunner):
self._core = core
self._native.core = core._core
lib.mCoreThreadStart(self._native)
self._core._wasReset = lib.mCoreThreadHasStarted(self._native)
self._core._was_reset = lib.mCoreThreadHasStarted(self._native)
def end(self):
if not lib.mCoreThreadHasStarted(self._native):
@ -48,11 +50,13 @@ class Thread(IRunner):
def unpause(self):
lib.mCoreThreadUnpause(self._native)
def isRunning(self):
@property
def running(self):
return bool(lib.mCoreThreadIsActive(self._native))
def isPaused(self):
@property
def paused(self):
return bool(lib.mCoreThreadIsPaused(self._native))
def useCore(self):
def use_core(self):
return ThreadCoreOwner(self)

View File

@ -3,14 +3,15 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from ._pylib import ffi, lib
from ._pylib import ffi, lib # pylint: disable=no-name-in-module
from . import image
class Tile:
def __init__(self, data):
self.buffer = data
def toImage(self):
def to_image(self):
i = image.Image(8, 8)
self.composite(i, 0, 0)
return i
@ -19,19 +20,22 @@ class Tile:
for iy in range(8):
ffi.memmove(ffi.addressof(i.buffer, x + (iy + y) * i.stride), ffi.addressof(self.buffer, iy * 8), 8 * ffi.sizeof("color_t"))
class CacheSet:
def __init__(self, core):
self.core = core
self.cache = ffi.gc(ffi.new("struct mCacheSet*"), core._deinitCache)
core._initCache(self.cache)
self.cache = ffi.gc(ffi.new("struct mCacheSet*"), core._deinit_cache)
core._init_cache(self.cache)
class TileView:
def __init__(self, cache):
self.cache = cache
def getTile(self, tile, palette):
def get_tile(self, tile, palette):
return Tile(lib.mTileCacheGetTile(self.cache, tile, palette))
class MapView:
def __init__(self, cache):
self.cache = cache
@ -54,6 +58,7 @@ class MapView:
ffi.memmove(ffi.addressof(i.buffer, i.stride * y), row, self.width * 8 * ffi.sizeof("color_t"))
return i
class Sprite(object):
def constitute(self, tileView, tilePitch):
i = image.Image(self.width, self.height, alpha=True)

View File

@ -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 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
def open(f): # pylint: disable=redefined-builtin
handle = ffi.new_handle(f)
vf = VFile(lib.VFileFromPython(handle), _no_gc=(f, handle))
return vf
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)
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
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:
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 seek(self, offset, whence):
return self.handle.seek(self.handle, offset, whence)
def close(self):
return bool(self.handle.close(self.handle))
def read(self, buffer, size):
return self.handle.read(self.handle, buffer, size)
def seek(self, offset, whence):
return self.handle.seek(self.handle, offset, whence)
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(self, buffer, size):
return self.handle.read(self.handle, buffer, size)
def readline(self, buffer, size):
return self.handle.readline(self.handle, buffer, size)
def 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 write(self, buffer, size):
return self.handle.write(self.handle, buffer, size)
def readline(self, buffer, size):
return self.handle.readline(self.handle, buffer, size)
def map(self, size, flags):
return self.handle.map(self.handle, size, flags)
def write(self, buffer, size):
return self.handle.write(self.handle, buffer, size)
def unmap(self, memory, size):
self.handle.unmap(self.handle, memory, size)
def map(self, size, flags):
return self.handle.map(self.handle, size, flags)
def truncate(self, size):
self.handle.truncate(self.handle, size)
def unmap(self, memory, size):
self.handle.unmap(self.handle, memory, size)
def size(self):
return self.handle.size(self.handle)
def truncate(self, size):
self.handle.truncate(self.handle, size)
def sync(self, buffer, size):
return self.handle.sync(self.handle, buffer, size)
def size(self):
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]
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 yaml
mgba.log.installDefault(mgba.log.NullLogger())
mgba.log.install_default(mgba.log.NullLogger())
def flatten(d):
l = []
@ -18,7 +18,7 @@ def flatten(d):
def pytest_generate_tests(metafunc):
if 'vtest' in metafunc.fixturenames:
tests = cinema.test.gatherTests(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'cinema'))
tests = cinema.test.gather_tests(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'cinema'))
testList = flatten(tests)
params = []
for test in testList:
@ -34,9 +34,9 @@ def vtest(request):
return request.param
def test_video(vtest, pytestconfig):
vtest.setUp()
vtest.setup()
if pytestconfig.getoption('--rebaseline'):
vtest.generateBaseline()
vtest.generate_baseline()
else:
try:
vtest.test()

View File

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

View File

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

View File

@ -204,13 +204,13 @@ CoreController::~CoreController() {
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::start() {
}
void CoreController::stop() {
setSync(false);
#ifdef USE_DEBUGGERS
setDebugger(nullptr);
#endif

View File

@ -10,6 +10,7 @@
#include "GamepadButtonEvent.h"
#include "VFileDevice.h"
#include <QAction>
#include <QDateTime>
#include <QKeyEvent>
#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(&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 @@ QString Window::getFilters() const {
QStringList gbFormats{
"*.gb",
"*.gbc",
"*.sgb",
#if defined(USE_LIBZIP) || defined(USE_ZLIB)
"*.zip",
#endif
@ -726,8 +729,6 @@ void Window::gameStarted() {
return;
}
#endif
multiplayerChanged();
updateTitle();
QSize size = m_controller->screenDimensions();
m_screenWidget->setDimensions(size.width(), size.height());
m_config->updateOption("lockIntegerScaling");
@ -744,10 +745,16 @@ void Window::gameStarted() {
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 @@ void Window::gameStarted() {
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 @@ void Window::gameStopped() {
m_audioChannels->clear();
m_fpsTimer.stop();
m_frameTimer.stop();
m_focusCheck.stop();
emit paused(false);
@ -944,19 +950,27 @@ void Window::mustRestart() {
}
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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,17 +1808,19 @@ void Window::setController(CoreController* controller, const QString& fname) {
if (!controller) {
return;
}
if (m_controller) {
m_controller->stop();
QTimer::singleShot(0, this, [this, controller, fname]() {
setController(controller, fname);
});
return;
}
if (!fname.isEmpty()) {
setWindowFilePath(fname);
appendMRU(fname);
}
if (m_controller) {
m_controller->disconnect(this);
m_controller->stop();
m_controller.reset();
}
m_controller = std::shared_ptr<CoreController>(controller);
m_inputController.recalibrateAxes();
m_controller->setInputController(&m_inputController);

View File

@ -5,6 +5,7 @@
* 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 @@ private slots:
void mustRestart();
void recordFrame();
void delimitFrames();
void showFPS();
void focusCheck();
@ -137,7 +139,8 @@ private slots:
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 @@ private:
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;

View File

@ -37,8 +37,10 @@ Q_IMPORT_PLUGIN(AVFServicePlugin);
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;

View File

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

View File

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