all repos — mgba @ 54579380be0bcbcd6c8330f19b4e278599972aa9

mGBA Game Boy Advance Emulator

Merge branch 'master' (early part) into medusa
Vicki Pfau vi@endrift.com
Sat, 28 Sep 2019 15:45:35 -0700
commit

54579380be0bcbcd6c8330f19b4e278599972aa9

parent

91d2c7ec323bfae064161e42852300a3d641b9c5

73 files changed, 2431 insertions(+), 796 deletions(-)

jump to
M CHANGESCHANGES

@@ -27,10 +27,14 @@ - Debugger: Add unary operators and memory dereferencing

- GB: Expose platform information to CLI debugger - Support Discord Rich Presence - Debugger: Add tracing to file - - Map viewer supports bitmapped GBA modes + - Enhanced map viewer, supporting bitmapped GBA modes and more displayed info - OpenGL renderer with high-resolution upscaling support - Experimental high level "XQ" audio for most GBA games - Interframe blending for games that use flicker effects + - Frame inspector for dissecting and debugging rendering + - Switch: Option to use built-in brightness sensor for Boktai + - Ports: Ability to enable or disable all SGB features (closes mgba.io/i/1205) + - Ports: Ability to crop SGB borders off screen (closes mgba.io/i/1204) Emulation fixes: - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: Reset now reloads multiboot ROMs

@@ -38,11 +42,16 @@ - GBA BIOS: Fix multiboot entry point (fixes Magic Floor)

- GB Video: Delay LYC STAT check (fixes mgba.io/i/1331) - GB Video: Fix window being enabled mid-scanline (fixes mgba.io/i/1328) - GB I/O: Filter IE top bits properly (fixes mgba.io/i/1329) + - GBA Video: Fix wrapped sprite mosaic clamping (fixes mgba.io/i/1432) + - GBA Memory: Fix STM to VRAM (fixes mgba.io/i/1430) Other fixes: - Qt: Fix some Qt display driver race conditions - Core: Improved lockstep driver reliability (Le Hoang Quyen) - Switch: Fix threading-related crash on second launch - Qt: Fix FPS target maxing out at 59.727 (fixes mgba.io/i/1421) + - Core: Fix crashes if core directories aren't set + - Qt: Cap audio buffer size to 8192 (fixes mgba.io/i/1433) + - GB Serialize: Fix loading non-BIOS state from BIOS (fixes mgba.io/i/1280) Misc: - GBA Savedata: EEPROM performance fixes - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash

@@ -64,6 +73,8 @@ - Qt: Add native FPS button to settings view

- Qt: Improve sync code - Switch: Dynamic display resizing - Qt: Make mute menu option also toggle fast-forward mute (fixes mgba.io/i/1424) + - Vita: L2/R2 and L3/R3 can now be mapped on PSTV (fixes mgba.io/i/1292) + - mGUI: Remember name and position of last loaded game 0.7.2: (2019-05-25) Emulation fixes:
M CMakeLists.txtCMakeLists.txt

@@ -1029,7 +1029,7 @@

if(BUILD_LIBRETRO) file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c) add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC}) - add_dependencies(${BINARY_NAME} version-info) + add_dependencies(${BINARY_NAME}_libretro version-info) set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "__LIBRETRO__;COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2") target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB}) if(MSVC)
M README.mdREADME.md

@@ -142,6 +142,7 @@ - mgba/switch

- mgba/ubuntu:xenial - mgba/ubuntu:bionic - mgba/ubuntu:cosmic +- mgba/ubuntu:disco - mgba/vita - mgba/wii - mgba/windows:w32
M README_DE.mdREADME_DE.md

@@ -124,6 +124,7 @@ - mgba/switch

- mgba/ubuntu:xenial - mgba/ubuntu:bionic - mgba/ubuntu:cosmic +- mgba/ubuntu:disco - mgba/vita - mgba/wii - mgba/windows:w32
M include/mgba-util/gui/file-select.hinclude/mgba-util/gui/file-select.h

@@ -14,7 +14,7 @@ #include <mgba-util/gui.h>

struct VFile; -bool GUISelectFile(struct GUIParams*, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)); +bool GUISelectFile(struct GUIParams*, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect); CXX_GUARD_END
M include/mgba/feature/video-logger.hinclude/mgba/feature/video-logger.h

@@ -35,6 +35,11 @@ LOGGER_EVENT_RESET,

LOGGER_EVENT_GET_PIXELS, }; +enum mVideoLoggerInjectionPoint { + LOGGER_INJECTION_IMMEDIATE = 0, + LOGGER_INJECTION_FIRST_SCANLINE, +}; + struct mVideoLoggerDirtyInfo { enum mVideoLoggerDirtyType type; uint32_t address;

@@ -97,6 +102,7 @@ void mVideoLoggerRendererFlush(struct mVideoLogger* logger);

void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger); bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block); +bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger); struct mVideoLogContext; void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId);

@@ -104,6 +110,7 @@

struct mCore; struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core); +void mVideoLogContextSetCompression(struct mVideoLogContext*, bool enable); void mVideoLogContextSetOutput(struct mVideoLogContext*, struct VFile*); void mVideoLogContextWriteHeader(struct mVideoLogContext*, struct mCore* core);

@@ -114,6 +121,12 @@ void mVideoLogContextRewind(struct mVideoLogContext*, struct mCore*);

void* mVideoLogContextInitialState(struct mVideoLogContext*, size_t* size); int mVideoLoggerAddChannel(struct mVideoLogContext*); + +void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint); +void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask); +void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value); +void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value); +void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value); struct mCore* mVideoLogCoreFind(struct VFile*);
M include/mgba/internal/gb/gb.hinclude/mgba/internal/gb/gb.h

@@ -150,6 +150,7 @@ void GBDestroy(struct GB* gb);

void GBReset(struct LR35902Core* cpu); void GBSkipBIOS(struct GB* gb); +void GBMapBIOS(struct GB* gb); void GBUnmapBIOS(struct GB* gb); void GBDetectModel(struct GB* gb);
M include/mgba/internal/gba/hardware.hinclude/mgba/internal/gba/hardware.h

@@ -74,6 +74,7 @@ DECL_BITFIELD(RTCStatus2, uint8_t);

DECL_BITS(RTCStatus2, INT1, 0, 4); DECL_BIT(RTCStatus2, INT2, 6); +#ifndef PYCPARSE #pragma pack(push, 1) struct GBARTC { int32_t bytesRemaining;

@@ -90,6 +91,9 @@ uint8_t alarm2[3];

uint8_t time[7]; }; #pragma pack(pop) +#else +struct GBATRC; +#endif struct GBAGBPKeyCallback { struct mKeyCallback d;
M include/mgba/internal/gba/renderers/common.hinclude/mgba/internal/gba/renderers/common.h

@@ -16,6 +16,7 @@ struct GBAVideoRendererSprite {

struct GBAObj obj; int16_t y; int16_t endY; + int8_t index; }; int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY, int masterHeight, bool combinedObjSort);
M include/mgba/internal/gba/renderers/video-software.hinclude/mgba/internal/gba/renderers/video-software.h

@@ -45,6 +45,7 @@ color_t* extPalette;

color_t* variantPalette; int32_t offsetX; int32_t offsetY; + bool highlight; }; enum {

@@ -117,6 +118,8 @@ color_t normalPalette[512];

color_t variantPalette[512]; color_t* objExtPalette; color_t* objExtVariantPalette; + color_t highlightPalette[512]; + color_t highlightVariantPalette[512]; uint16_t blda; uint16_t bldb;

@@ -165,6 +168,8 @@ int masterScanlines;

int masterBright; int masterBrightY; + + uint8_t lastHighlightAmount; }; void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer);
M include/mgba/internal/gba/sio.hinclude/mgba/internal/gba/sio.h

@@ -33,6 +33,23 @@ JOYSTAT_TRANS_BIT = 8,

JOYSTAT_RECV_BIT = 2, }; +DECL_BITFIELD(GBASIONormal, uint16_t); +DECL_BIT(GBASIONormal, Sc, 0); +DECL_BIT(GBASIONormal, InternalSc, 1); +DECL_BIT(GBASIONormal, Si, 2); +DECL_BIT(GBASIONormal, IdleSo, 3); +DECL_BIT(GBASIONormal, Start, 7); +DECL_BIT(GBASIONormal, Length, 12); +DECL_BIT(GBASIONormal, Irq, 14); +DECL_BITFIELD(GBASIOMultiplayer, uint16_t); +DECL_BITS(GBASIOMultiplayer, Baud, 0, 2); +DECL_BIT(GBASIOMultiplayer, Slave, 2); +DECL_BIT(GBASIOMultiplayer, Ready, 3); +DECL_BITS(GBASIOMultiplayer, Id, 4, 2); +DECL_BIT(GBASIOMultiplayer, Error, 6); +DECL_BIT(GBASIOMultiplayer, Busy, 8); +DECL_BIT(GBASIOMultiplayer, Irq, 14); + struct GBASIODriverSet { struct GBASIODriver* normal; struct GBASIODriver* multiplayer;

@@ -47,36 +64,7 @@ struct GBASIODriverSet drivers;

struct GBASIODriver* activeDriver; uint16_t rcnt; - // TODO: Convert to bitfields - union { - struct { - unsigned sc : 1; - unsigned internalSc : 1; - unsigned si : 1; - unsigned idleSo : 1; - unsigned : 3; - unsigned start : 1; - unsigned : 4; - unsigned length : 1; - unsigned : 1; - unsigned irq : 1; - unsigned : 1; - } normalControl; - - struct { - unsigned baud : 2; - unsigned slave : 1; - unsigned ready : 1; - unsigned id : 2; - unsigned error : 1; - unsigned busy : 1; - unsigned : 6; - unsigned irq : 1; - unsigned : 1; - } multiplayerControl; - - uint16_t siocnt; - }; + uint16_t siocnt; }; void GBASIOInit(struct GBASIO* sio);
M include/mgba/internal/gba/video.hinclude/mgba/internal/gba/video.h

@@ -12,6 +12,7 @@ CXX_GUARD_START

#include <mgba/core/log.h> #include <mgba/core/timing.h> +#include <mgba/gba/interface.h> mLOG_DECLARE_CATEGORY(GBA_VIDEO);

@@ -84,20 +85,20 @@ GBAObjAttributesC c;

uint16_t d; }; +struct GBAOAMMatrix { + int16_t padding0[3]; + int16_t a; + int16_t padding1[3]; + int16_t b; + int16_t padding2[3]; + int16_t c; + int16_t padding3[3]; + int16_t d; +}; + union GBAOAM { struct GBAObj obj[128]; - - struct GBAOAMMatrix { - int16_t padding0[3]; - int16_t a; - int16_t padding1[3]; - int16_t b; - int16_t padding2[3]; - int16_t c; - int16_t padding3[3]; - int16_t d; - } mat[32]; - + struct GBAOAMMatrix mat[32]; uint16_t raw[512]; };

@@ -197,6 +198,11 @@ struct mCacheSet* cache;

bool disableBG[4]; bool disableOBJ; + + bool highlightBG[4]; + bool highlightOBJ[128]; + color_t highlightColor; + uint8_t highlightAmount; }; struct GBAVideo {
M src/core/core.csrc/core/core.c

@@ -164,10 +164,16 @@ return ret;

} bool mCoreAutoloadSave(struct mCore* core) { + if (!core->dirs.save) { + return false; + } return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR)); } bool mCoreAutoloadPatch(struct mCore* core) { + if (!core->dirs.patch) { + return false; + } return core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY)) || core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) || core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY));

@@ -230,6 +236,9 @@ return success;

} struct VFile* mCoreGetState(struct mCore* core, int slot, bool write) { + if (!core->dirs.state) { + return NULL; + } char name[PATH_MAX + 14]; // Quash warning snprintf(name, sizeof(name), "%s.ss%i", core->dirs.baseName, slot); return core->dirs.state->openFile(core->dirs.state, name, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
M src/feature/gui/gui-config.csrc/feature/gui/gui-config.c

@@ -119,10 +119,34 @@ },

.nStates = 2 }; *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Enable SGB features", + .data = "sgb.model", + .submenu = 0, + .state = true, + .validStates = (const char*[]) { + "Off", "On" + }, + .stateMappings = (const struct GUIVariant[]) { + GUI_V_S("DMG"), + GUI_V_S("SGB"), + }, + .nStates = 2 + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { .title = "Enable SGB borders", .data = "sgb.borders", .submenu = 0, .state = true, + .validStates = (const char*[]) { + "Off", "On" + }, + .nStates = 2 + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Crop SGB borders", + .data = "sgb.borderCrop", + .submenu = 0, + .state = false, .validStates = (const char*[]) { "Off", "On" },

@@ -173,8 +197,6 @@ if (!item->validStates || !item->data) {

continue; } if (item->stateMappings) { - item->state = 0; - size_t j; for (j = 0; j < item->nStates; ++j) { const struct GUIVariant* v = &item->stateMappings[j];

@@ -281,7 +303,7 @@ continue;

} if (!strcmp(item->data, "gba.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), _biosNamed, GBAIsBIOS)) { + if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), _biosNamed, GBAIsBIOS, NULL)) { gbaBiosPath[0] = '\0'; } continue;

@@ -289,21 +311,21 @@ }

#ifdef M_CORE_GB if (!strcmp(item->data, "gb.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), _biosNamed, GBIsBIOS)) { + if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), _biosNamed, GBIsBIOS, NULL)) { gbBiosPath[0] = '\0'; } continue; } if (!strcmp(item->data, "gbc.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), _biosNamed, GBIsBIOS)) { + if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), _biosNamed, GBIsBIOS, NULL)) { gbcBiosPath[0] = '\0'; } continue; } if (!strcmp(item->data, "sgb.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), _biosNamed, GBIsBIOS)) { + if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), _biosNamed, GBIsBIOS, NULL)) { sgbBiosPath[0] = '\0'; } continue;
M src/feature/gui/gui-runner.csrc/feature/gui/gui-runner.c

@@ -628,10 +628,18 @@ }

} while (true) { char path[PATH_MAX]; - if (!GUISelectFile(&runner->params, path, sizeof(path), _testExtensions, NULL)) { + const char* preselect = mCoreConfigGetValue(&runner->config, "lastGame"); + if (preselect) { + preselect = strrchr(preselect, '/'); + } + if (preselect) { + ++preselect; + } + if (!GUISelectFile(&runner->params, path, sizeof(path), _testExtensions, NULL, preselect)) { break; } mCoreConfigSetValue(&runner->config, "lastDirectory", runner->params.currentPath); + mCoreConfigSetValue(&runner->config, "lastGame", path); mCoreConfigSave(&runner->config); mGUIRun(runner, path); }
M src/feature/video-logger.csrc/feature/video-logger.c

@@ -84,6 +84,11 @@ bool inflating;

z_stream inflateStream; #endif + bool injecting; + enum mVideoLoggerInjectionPoint injectionPoint; + uint32_t ignorePackets; + + struct CircleBuffer injectedBuffer; struct CircleBuffer buffer; };

@@ -94,6 +99,7 @@ uint32_t nChannels;

struct mVideoLogChannel channels[mVL_MAX_CHANNELS]; bool write; + bool compression; uint32_t activeChannel; struct VFile* backing; };

@@ -285,14 +291,28 @@ logger->writeData(logger, data, length);

} bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) { + struct mVideoLogChannel* channel = logger->dataContext; + uint32_t ignorePackets = 0; + if (channel && channel->injectionPoint == LOGGER_INJECTION_IMMEDIATE && !channel->injecting) { + mVideoLoggerRendererRunInjected(logger); + ignorePackets = channel->ignorePackets; + } struct mVideoLoggerDirtyInfo item = {0}; while (logger->readData(logger, &item, sizeof(item), block)) { + if (ignorePackets & (1 << item.type)) { + continue; + } switch (item.type) { + case DIRTY_SCANLINE: + if (channel && channel->injectionPoint == LOGGER_INJECTION_FIRST_SCANLINE && !channel->injecting && item.address == 0) { + mVideoLoggerRendererRunInjected(logger); + ignorePackets = channel->ignorePackets; + } + // Fall through case DIRTY_REGISTER: case DIRTY_PALETTE: case DIRTY_OAM: case DIRTY_VRAM: - case DIRTY_SCANLINE: case DIRTY_FLUSH: case DIRTY_FRAME: case DIRTY_RANGE:

@@ -308,15 +328,34 @@ }

return !block; } +bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injecting = true; + bool res = mVideoLoggerRendererRun(logger, false); + channel->injecting = false; + return res; +} + +void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint injectionPoint) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injectionPoint = injectionPoint; +} + +void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->ignorePackets = mask; +} + static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) { struct mVideoLogChannel* channel = logger->dataContext; return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length; } static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) { - UNUSED(logger); - UNUSED(data); - UNUSED(length); + struct mVideoLogChannel* channel = logger->dataContext; + if (channel->injecting) { + return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length; + } return false; }

@@ -465,6 +504,12 @@ context->write = !!core;

context->initialStateSize = 0; context->initialState = NULL; +#ifdef USE_ZLIB + context->compression = true; +#else + context->compression = false; +#endif + if (core) { context->initialStateSize = core->stateSize(core); context->initialState = anonymousMemoryMap(context->initialStateSize);

@@ -482,6 +527,10 @@ vf->truncate(vf, 0);

vf->seek(vf, 0, SEEK_SET); } +void mVideoLogContextSetCompression(struct mVideoLogContext* context, bool compression) { + context->compression = compression; +} + void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) { struct mVideoLogHeader header = { { 0 } }; memcpy(header.magic, mVL_MAGIC, sizeof(header.magic));

@@ -499,21 +548,24 @@ if (context->initialState) {

struct mVLBlockHeader chheader = { 0 }; STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType); #ifdef USE_ZLIB - STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags); + if (context->compression) { + STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags); - struct VFile* vfm = VFileMemChunk(NULL, 0); - struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize); - _compress(vfm, src); - src->close(src); - STORE_32LE(vfm->size(vfm), 0, &chheader.length); - context->backing->write(context->backing, &chheader, sizeof(chheader)); - _copyVf(context->backing, vfm); - vfm->close(vfm); -#else - STORE_32LE(context->initialStateSize, 0, &chheader.length); - context->backing->write(context->backing, &chheader, sizeof(chheader)); - context->backing->write(context->backing, context->initialState, context->initialStateSize); + struct VFile* vfm = VFileMemChunk(NULL, 0); + struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize); + _compress(vfm, src); + src->close(src); + STORE_32LE(vfm->size(vfm), 0, &chheader.length); + context->backing->write(context->backing, &chheader, sizeof(chheader)); + _copyVf(context->backing, vfm); + vfm->close(vfm); + } else #endif + { + STORE_32LE(context->initialStateSize, 0, &chheader.length); + context->backing->write(context->backing, &chheader, sizeof(chheader)); + context->backing->write(context->backing, context->initialState, context->initialStateSize); + } } size_t i;

@@ -609,6 +661,7 @@ off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);

size_t i; for (i = 0; i < context->nChannels; ++i) { + CircleBufferInit(&context->channels[i].injectedBuffer, BUFFER_BASE_SIZE); CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE); context->channels[i].bufferRemaining = 0; context->channels[i].currentPointer = pointer;

@@ -647,9 +700,10 @@ #endif

static void _flushBuffer(struct mVideoLogContext* context) { #ifdef USE_ZLIB - // TODO: Make optional - _flushBufferCompressed(context); - return; + if (context->compression) { + _flushBufferCompressed(context); + return; + } #endif struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;

@@ -688,6 +742,7 @@ }

size_t i; for (i = 0; i < context->nChannels; ++i) { + CircleBufferDeinit(&context->channels[i].injectedBuffer); CircleBufferDeinit(&context->channels[i].buffer); #ifdef USE_ZLIB if (context->channels[i].inflating) {

@@ -718,6 +773,7 @@ off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);

size_t i; for (i = 0; i < context->nChannels; ++i) { + CircleBufferClear(&context->channels[i].injectedBuffer); CircleBufferClear(&context->channels[i].buffer); context->channels[i].bufferRemaining = 0; context->channels[i].currentPointer = pointer;

@@ -744,10 +800,35 @@ }

int chid = context->nChannels; ++context->nChannels; context->channels[chid].p = context; + CircleBufferInit(&context->channels[chid].injectedBuffer, BUFFER_BASE_SIZE); CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE); + context->channels[chid].injecting = false; + context->channels[chid].injectionPoint = LOGGER_INJECTION_IMMEDIATE; + context->channels[chid].ignorePackets = 0; return chid; } +void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injecting = true; + mVideoLoggerRendererWriteVideoRegister(logger, address, value); + channel->injecting = false; +} + +void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injecting = true; + mVideoLoggerRendererWritePalette(logger, address, value); + channel->injecting = false; +} + +void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injecting = true; + mVideoLoggerRendererWriteOAM(logger, address, value); + channel->injecting = false; +} + #ifdef USE_ZLIB static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) { uint8_t fbuffer[0x400];

@@ -900,12 +981,16 @@ unsigned channelId = channel - context->channels;

if (channelId >= mVL_MAX_CHANNELS) { return 0; } - if (CircleBufferSize(&channel->buffer) >= length) { - return CircleBufferRead(&channel->buffer, data, length); + struct CircleBuffer* buffer = &channel->buffer; + if (channel->injecting) { + buffer = &channel->injectedBuffer; + } + if (CircleBufferSize(buffer) >= length) { + return CircleBufferRead(buffer, data, length); } ssize_t size = 0; - if (CircleBufferSize(&channel->buffer)) { - size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer)); + if (CircleBufferSize(buffer)) { + size = CircleBufferRead(buffer, data, CircleBufferSize(buffer)); if (size <= 0) { return size; }

@@ -915,7 +1000,7 @@ }

if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) { return size; } - size += CircleBufferRead(&channel->buffer, data, length); + size += CircleBufferRead(buffer, data, length); return size; }

@@ -929,16 +1014,20 @@ if (channelId != context->activeChannel) {

_flushBuffer(context); context->activeChannel = channelId; } - if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) { + struct CircleBuffer* buffer = &channel->buffer; + if (channel->injecting) { + buffer = &channel->injectedBuffer; + } + if (CircleBufferCapacity(buffer) - CircleBufferSize(buffer) < length) { _flushBuffer(context); - if (CircleBufferCapacity(&channel->buffer) < length) { - CircleBufferDeinit(&channel->buffer); - CircleBufferInit(&channel->buffer, toPow2(length << 1)); + if (CircleBufferCapacity(buffer) < length) { + CircleBufferDeinit(buffer); + CircleBufferInit(buffer, toPow2(length << 1)); } } - ssize_t read = CircleBufferWrite(&channel->buffer, data, length); - if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) { + ssize_t read = CircleBufferWrite(buffer, data, length); + if (CircleBufferCapacity(buffer) == CircleBufferSize(buffer)) { _flushBuffer(context); } return read;
M src/gb/core.csrc/gb/core.c

@@ -902,9 +902,11 @@

static void _GBCoreEndVideoLog(struct mCore* core) { struct GBCore* gbcore = (struct GBCore*) core; struct GB* gb = core->board; - GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); - free(gbcore->proxyRenderer.logger); - gbcore->proxyRenderer.logger = NULL; + if (gbcore->proxyRenderer.logger) { + GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); + free(gbcore->proxyRenderer.logger); + gbcore->proxyRenderer.logger = NULL; + } } #endif

@@ -1007,6 +1009,7 @@ if (!mVideoLoggerRendererRun(gbcore->proxyRenderer.logger, true)) {

GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); mVideoLogContextRewind(gbcore->logContext, core); GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer); + gb->earlyExit = true; } }

@@ -1022,6 +1025,7 @@ memset(&gbcore->logCallbacks, 0, sizeof(gbcore->logCallbacks));

gbcore->logCallbacks.videoFrameStarted = _GBVLPStartFrameCallback; gbcore->logCallbacks.context = core; core->addCoreCallbacks(core, &gbcore->logCallbacks); + core->videoLogger = gbcore->proxyRenderer.logger; return true; }
M src/gb/gb.csrc/gb/gb.c

@@ -419,14 +419,7 @@ if (!GBIsBIOS(gb->biosVf)) {

gb->biosVf->close(gb->biosVf); gb->biosVf = NULL; } else { - gb->biosVf->seek(gb->biosVf, 0, SEEK_SET); - gb->memory.romBase = malloc(GB_SIZE_CART_BANK0); - ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0); - memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size); - if (size > 0x100) { - memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge)); - } - + GBMapBIOS(gb); cpu->a = 0; cpu->f.packed = 0; cpu->c = 0;

@@ -560,6 +553,16 @@ GBIOWrite(gb, REG_LCDC, 0x91);

if (gb->biosVf) { GBUnmapBIOS(gb); + } +} + +void GBMapBIOS(struct GB* gb) { + gb->biosVf->seek(gb->biosVf, 0, SEEK_SET); + gb->memory.romBase = malloc(GB_SIZE_CART_BANK0); + ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0); + memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size); + if (size > 0x100) { + memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge)); } }
M src/gb/io.csrc/gb/io.c

@@ -185,8 +185,10 @@ GBIOWrite(gb, REG_NR50, 0x77);

GBIOWrite(gb, REG_NR51, 0xF3); if (!gb->biosVf) { GBIOWrite(gb, REG_LCDC, 0x91); + gb->memory.io[0x50] = 1; } else { GBIOWrite(gb, REG_LCDC, 0x00); + gb->memory.io[0x50] = 0xFF; } GBIOWrite(gb, REG_SCY, 0x00); GBIOWrite(gb, REG_SCX, 0x00);
M src/gb/serialize.csrc/gb/serialize.c

@@ -135,6 +135,16 @@ LOAD_16LE(ucheck16, 0, &state->video.ocpIndex);

if (ucheck16 >= 0x40) { mLOG(GB_STATE, WARN, "Savestate is corrupted: OCPS is out of range"); } + bool differentBios = !gb->biosVf || gb->model != state->model; + if (state->io[0x50] == 0xFF) { + if (differentBios) { + mLOG(GB_STATE, WARN, "Incompatible savestate, please restart with correct BIOS in %s mode", GBModelToName(state->model)); + error = true; + } else { + // TODO: Make it work correctly + mLOG(GB_STATE, WARN, "Loading savestate in BIOS. This may not work correctly"); + } + } if (error) { return false; }

@@ -186,6 +196,12 @@ GBVideoDeserialize(&gb->video, state);

GBIODeserialize(gb, state); GBTimerDeserialize(&gb->timer, state); GBAudioDeserialize(&gb->audio, state); + + if (gb->memory.io[0x50] == 0xFF) { + GBMapBIOS(gb); + } else { + GBUnmapBIOS(gb); + } if (gb->model & GB_MODEL_SGB && canSgb) { GBSGBDeserialize(gb, state);
M src/gb/video.csrc/gb/video.c

@@ -343,19 +343,19 @@ if (video->p->cpu->executionState != LR35902_CORE_FETCH) {

mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3)); return; } + if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) { + mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); + } - GBFrameEnded(video->p); - mCoreSyncPostFrame(video->p->sync); --video->frameskipCounter; if (video->frameskipCounter < 0) { video->renderer->finishFrame(video->renderer); video->frameskipCounter = video->frameskip; } + GBFrameEnded(video->p); + mCoreSyncPostFrame(video->p->sync); ++video->frameCounter; - if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) { - mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); - } GBFrameStarted(video->p); }
M src/gba/core.csrc/gba/core.c

@@ -137,8 +137,11 @@ struct GBAVideoSoftwareRenderer renderer;

#if defined(BUILD_GLES2) || defined(BUILD_GLES3) struct GBAVideoGLRenderer glRenderer; #endif +#ifndef MINIMAL_CORE + struct GBAVideoProxyRenderer vlProxy; struct GBAVideoProxyRenderer proxyRenderer; struct mVideoLogContext* logContext; +#endif struct mCoreCallbacks logCallbacks; #ifndef DISABLE_THREADING struct mVideoThreadProxy threadProxy;

@@ -170,7 +173,9 @@ core->videoLogger = NULL;

gbacore->overrides = NULL; gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL; +#ifndef MINIMAL_CORE gbacore->logContext = NULL; +#endif gbacore->audioMixer = NULL; GBACreate(gba);

@@ -192,7 +197,10 @@

#ifndef DISABLE_THREADING mVideoThreadProxyCreate(&gbacore->threadProxy); #endif +#ifndef MINIMAL_CORE + gbacore->vlProxy.logger = NULL; gbacore->proxyRenderer.logger = NULL; +#endif gbacore->keys = 0; gba->keySource = &gbacore->keys;

@@ -475,11 +483,13 @@ core->videoLogger = &gbacore->threadProxy.d;

} } #endif +#ifndef MINIMAL_CORE if (core->videoLogger) { gbacore->proxyRenderer.logger = core->videoLogger; GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer); renderer = &gbacore->proxyRenderer.d; } +#endif GBAVideoAssociateRenderer(&gba->video, renderer); }

@@ -1000,21 +1010,23 @@ state->id = 0;

state->cpu.gprs[ARM_PC] = BASE_WORKING_RAM; int channelId = mVideoLoggerAddChannel(context); - gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); - mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, false); - mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, context, channelId); - gbacore->proxyRenderer.logger->block = false; + gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger)); + mVideoLoggerRendererCreate(gbacore->vlProxy.logger, false); + mVideoLoggerAttachChannel(gbacore->vlProxy.logger, context, channelId); + gbacore->vlProxy.logger->block = false; - GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, &gbacore->renderer.d); - GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); + GBAVideoProxyRendererCreate(&gbacore->vlProxy, gba->video.renderer); + GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy); } static void _GBACoreEndVideoLog(struct mCore* core) { struct GBACore* gbacore = (struct GBACore*) core; struct GBA* gba = core->board; - GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); - free(gbacore->proxyRenderer.logger); - gbacore->proxyRenderer.logger = NULL; + if (gbacore->vlProxy.logger) { + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy); + free(gbacore->vlProxy.logger); + gbacore->vlProxy.logger = NULL; + } } #endif

@@ -1112,10 +1124,11 @@ struct mCore* core = context;

struct GBACore* gbacore = (struct GBACore*) core; struct GBA* gba = core->board; - if (!mVideoLoggerRendererRun(gbacore->proxyRenderer.logger, true)) { - GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); + if (!mVideoLoggerRendererRun(gbacore->vlProxy.logger, true)) { + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy); mVideoLogContextRewind(gbacore->logContext, core); - GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); + GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy); + gba->earlyExit = true; } }

@@ -1124,13 +1137,14 @@ struct GBACore* gbacore = (struct GBACore*) core;

if (!_GBACoreInit(core)) { return false; } - gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); - mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, true); - GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, NULL); + gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger)); + mVideoLoggerRendererCreate(gbacore->vlProxy.logger, true); + GBAVideoProxyRendererCreate(&gbacore->vlProxy, NULL); memset(&gbacore->logCallbacks, 0, sizeof(gbacore->logCallbacks)); gbacore->logCallbacks.videoFrameStarted = _GBAVLPStartFrameCallback; gbacore->logCallbacks.context = core; core->addCoreCallbacks(core, &gbacore->logCallbacks); + core->videoLogger = gbacore->vlProxy.logger; return true; }

@@ -1145,8 +1159,8 @@

static void _GBAVLPReset(struct mCore* core) { struct GBACore* gbacore = (struct GBACore*) core; struct GBA* gba = (struct GBA*) core->board; - if (gba->video.renderer == &gbacore->proxyRenderer.d) { - GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); + if (gba->video.renderer == &gbacore->vlProxy.d) { + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy); } else if (gbacore->renderer.outputBuffer) { struct GBAVideoRenderer* renderer = &gbacore->renderer.d; GBAVideoAssociateRenderer(&gba->video, renderer);

@@ -1154,7 +1168,7 @@ }

ARMReset(core->cpu); mVideoLogContextRewind(gbacore->logContext, core); - GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); + GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy); // Make sure CPU loop never spins GBAHalt(gba);

@@ -1170,7 +1184,7 @@ mVideoLogContextDestroy(core, gbacore->logContext);

gbacore->logContext = NULL; return false; } - mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, gbacore->logContext, 0); + mVideoLoggerAttachChannel(gbacore->vlProxy.logger, gbacore->logContext, 0); return true; }
M src/gba/extra/battlechip.csrc/gba/extra/battlechip.c

@@ -87,7 +87,7 @@ int32_t cycles;

if (gate->d.p->mode == SIO_NORMAL_32) { cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000; } else { - cycles = GBASIOCyclesPerTransfer[gate->d.p->multiplayerControl.baud][1]; + cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(gate->d.p->siocnt)][1]; } mTimingDeschedule(&gate->d.p->p->timing, &gate->event); mTimingSchedule(&gate->d.p->p->timing, &gate->event, cycles);

@@ -100,8 +100,8 @@

if (gate->d.p->mode == SIO_NORMAL_32) { gate->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0; gate->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0; - gate->d.p->normalControl.start = 0; - if (gate->d.p->normalControl.irq) { + gate->d.p->siocnt = GBASIONormalClearStart(gate->d.p->siocnt); + if (GBASIONormalIsIrq(gate->d.p->siocnt)) { GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate); } return;

@@ -112,8 +112,8 @@ uint16_t reply = 0xFFFF;

gate->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = cmd; gate->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; gate->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; - gate->d.p->multiplayerControl.busy = 0; - gate->d.p->multiplayerControl.id = 0; + gate->d.p->siocnt = GBASIOMultiplayerClearBusy(gate->d.p->siocnt); + gate->d.p->siocnt = GBASIOMultiplayerSetId(gate->d.p->siocnt, 0); mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state);

@@ -193,7 +193,7 @@ ++gate->state;

gate->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = reply; - if (gate->d.p->multiplayerControl.irq) { + if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) { GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate); } }
M src/gba/extra/proxy.csrc/gba/extra/proxy.c

@@ -44,6 +44,17 @@ renderer->d.disableBG[2] = false;

renderer->d.disableBG[3] = false; renderer->d.disableOBJ = false; + renderer->d.highlightBG[0] = false; + renderer->d.highlightBG[1] = false; + renderer->d.highlightBG[2] = false; + renderer->d.highlightBG[3] = false; + int i; + for (i = 0; i < 128; ++i) { + renderer->d.highlightOBJ[i] = false; + } + renderer->d.highlightColor = 0xFFFFFF; + renderer->d.highlightAmount = 0; + renderer->logger->context = renderer; renderer->logger->parsePacket = _parsePacket; renderer->logger->handleEvent = _handleEvent;

@@ -205,6 +216,12 @@ proxyRenderer->backend->disableBG[1] = proxyRenderer->d.disableBG[1];

proxyRenderer->backend->disableBG[2] = proxyRenderer->d.disableBG[2]; proxyRenderer->backend->disableBG[3] = proxyRenderer->d.disableBG[3]; proxyRenderer->backend->disableOBJ = proxyRenderer->d.disableOBJ; + proxyRenderer->backend->highlightBG[0] = proxyRenderer->d.highlightBG[0]; + proxyRenderer->backend->highlightBG[1] = proxyRenderer->d.highlightBG[1]; + proxyRenderer->backend->highlightBG[2] = proxyRenderer->d.highlightBG[2]; + proxyRenderer->backend->highlightBG[3] = proxyRenderer->d.highlightBG[3]; + memcpy(proxyRenderer->backend->highlightOBJ, proxyRenderer->d.highlightOBJ, sizeof(proxyRenderer->backend->highlightOBJ)); + proxyRenderer->backend->highlightAmount = proxyRenderer->d.highlightAmount; if (item->address < GBA_VIDEO_VERTICAL_PIXELS) { proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address); }
M src/gba/hardware.csrc/gba/hardware.c

@@ -577,10 +577,10 @@ tx = _gbpTxData[txPosition];

++gbp->p->gbpTxPosition; gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx; gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16; - if (gbp->d.p->normalControl.irq) { + if (GBASIONormalIsIrq(gbp->d.p->siocnt)) { GBARaiseIRQ(gbp->p->p, IRQ_SIO, cyclesLate); } - gbp->d.p->normalControl.start = 0; + gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt); gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080; }
M src/gba/memory.csrc/gba/memory.c

@@ -396,9 +396,10 @@ mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Load32: 0x%08X", address); \

value = 0; \ break; \ } \ - address &= 0x00017FFC; \ + LOAD_32(value, address & 0x00017FFC, gba->video.vram); \ + } else { \ + LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \ } \ - LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \ wait += waitstatesRegion[REGION_VRAM]; #define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);

@@ -530,9 +531,10 @@ mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Load16: 0x%08X", address);

value = 0; break; } - address &= 0x00017FFE; + LOAD_16(value, address & 0x00017FFE, gba->video.vram); + } else { + LOAD_16(value, address & 0x0001FFFE, gba->video.vram); } - LOAD_16(value, address & 0x0001FFFE, gba->video.vram); break; case REGION_OAM: LOAD_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw);

@@ -645,9 +647,10 @@ mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Load8: 0x%08X", address);

value = 0; break; } - address &= 0x00017FFF; + value = ((uint8_t*) gba->video.vram)[address & 0x00017FFF]; + } else { + value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF]; } - value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF]; break; case REGION_OAM: value = ((uint8_t*) gba->video.oam.raw)[address & (SIZE_OAM - 1)];

@@ -734,13 +737,19 @@ if ((address & (SIZE_VRAM | 0x00014000)) == SIZE_VRAM && (GBARegisterDISPCNTGetMode(gba->memory.io[REG_DISPCNT >> 1]) >= 3)) { \

mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store32: 0x%08X", address); \ break; \ } \ - address &= 0x00017FFC; \ - } \ - LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram); \ - if (oldValue != value) { \ - STORE_32(value, address & 0x0001FFFC, gba->video.vram); \ - gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \ - gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \ + LOAD_32(oldValue, address & 0x00017FFC, gba->video.vram); \ + if (oldValue != value) { \ + STORE_32(value, address & 0x00017FFC, gba->video.vram); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) + 2); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC)); \ + } \ + } else { \ + LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram); \ + if (oldValue != value) { \ + STORE_32(value, address & 0x0001FFFC, gba->video.vram); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \ + } \ } \ wait += waitstatesRegion[REGION_VRAM];

@@ -855,12 +864,17 @@ if ((address & (SIZE_VRAM | 0x00014000)) == SIZE_VRAM && (GBARegisterDISPCNTGetMode(gba->memory.io[REG_DISPCNT >> 1]) >= 3)) {

mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store16: 0x%08X", address); break; } - address &= 0x00017FFE; - } - LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram); - if (value != oldValue) { - STORE_16(value, address & 0x0001FFFE, gba->video.vram); - gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); + LOAD_16(oldValue, address & 0x00017FFE, gba->video.vram); + if (value != oldValue) { + STORE_16(value, address & 0x00017FFE, gba->video.vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE); + } + } else { + LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram); + if (value != oldValue) { + STORE_16(value, address & 0x0001FFFE, gba->video.vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); + } } break; case REGION_OAM:
M src/gba/renderers/common.csrc/gba/renderers/common.c

@@ -34,6 +34,7 @@ int y = GBAObjAttributesAGetY(obj.a) + offsetY;

sprites[oamMax].y = y; sprites[oamMax].endY = y + height; sprites[oamMax].obj = obj; + sprites[oamMax].index = i; ++oamMax; } }
M src/gba/renderers/gl.csrc/gba/renderers/gl.c

@@ -155,7 +155,7 @@ " }\n"

" if ((size & 1) == 1) {\n" " coord.y += coord.x & 256;\n" " }\n" - " coord &= ivec2(255, 511);\n" + " coord &= ivec2(255, 1023);\n" " int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n" " vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n" " int tileFlags = int(map.g * 15.9);\n"

@@ -442,7 +442,7 @@ "uniform ivec4 objwin;\n"

"uniform ivec4 mosaic;\n" "OUT(0) out vec4 color;\n" "OUT(1) out ivec4 flags;\n" - "OUT(2) out ivec3 window;\n" + "OUT(2) out ivec4 window;\n" "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"

@@ -467,7 +467,7 @@ " vec4 pix = renderTile((coord.x >> 3) + (coord.y >> 3) * stride, localPalette, coord & 7);\n"

" color = pix;\n" " flags = inflags;\n" " gl_FragDepth = float(flags.x) / 16.;\n" - " window = objwin.yzw;\n" + " window = ivec4(objwin.yzw, 0);\n" "}"; static const struct GBAVideoGLUniform _uniformsWindow[] = {

@@ -488,7 +488,7 @@ "uniform ivec2 blend;\n"

"uniform ivec3 flags;\n" "uniform ivec4 win0[160];\n" "uniform ivec4 win1[160];\n" - "OUT(0) out ivec3 window;\n" + "OUT(0) out ivec4 window;\n" "void crop(vec4 windowParams, int flags, inout ivec3 windowFlags) {\n" " bvec4 compare = lessThan(texCoord.xxyy, windowParams);\n"

@@ -526,7 +526,7 @@

"void main() {\n" " int dispflags = (dispcnt & 0x1F) | 0x20;\n" " if ((dispcnt & 0xE0) == 0) {\n" - " window = ivec3(dispflags, blend);\n" + " window = ivec4(dispflags, blend, 0);\n" " } else {\n" " ivec3 windowFlags = ivec3(flags.z, blend);\n" " if ((dispcnt & 0x40) != 0) { \n"

@@ -535,7 +535,7 @@ " }\n"

" if ((dispcnt & 0x20) != 0) { \n" " crop(interpolate(win0), flags.x, windowFlags);\n" " }\n" - " window = windowFlags;\n" + " window = ivec4(windowFlags, 0);\n" " }\n" "}\n";

@@ -650,6 +650,17 @@ renderer->d.disableBG[1] = false;

renderer->d.disableBG[2] = false; renderer->d.disableBG[3] = false; renderer->d.disableOBJ = false; + + renderer->d.highlightBG[0] = false; + renderer->d.highlightBG[1] = false; + renderer->d.highlightBG[2] = false; + renderer->d.highlightBG[3] = false; + int i; + for (i = 0; i < 128; ++i) { + renderer->d.highlightOBJ[i] = false; + } + renderer->d.highlightColor = 0xFFFFFF; + renderer->d.highlightAmount = 0; renderer->scale = 1; }

@@ -1369,7 +1380,7 @@ glViewport(0, 0, 1, GBA_VIDEO_VERTICAL_PIXELS);

glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]); glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); - glClearBufferfv(GL_COLOR, 0, (GLfloat[]) { ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 1.f }); + glClearBufferfv(GL_COLOR, 0, (GLfloat[]) { ((backdrop >> 16) & 0xF8) / 248., ((backdrop >> 8) & 0xF8) / 248., (backdrop & 0xF8) / 248., 1.f }); glClearBufferiv(GL_COLOR, 1, (GLint[]) { 32, glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4), glRenderer->blda, 0 }); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
M src/gba/renderers/software-mode0.csrc/gba/renderers/software-mode0.c

@@ -613,16 +613,21 @@

uint32_t screenBase; uint32_t charBase; int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); - color_t* mainPalette; + color_t* mainPalette = renderer->normalPalette; if (background->multipalette && background->extPalette) { mainPalette = background->extPalette; if (variant) { mainPalette = background->variantPalette; } } else { - mainPalette = renderer->normalPalette; + if (renderer->d.highlightAmount && background->highlight) { + mainPalette = renderer->highlightPalette; + } if (variant) { mainPalette = renderer->variantPalette; + if (renderer->d.highlightAmount && background->highlight) { + mainPalette = renderer->highlightVariantPalette; + } } } color_t* palette = mainPalette;
M src/gba/renderers/software-obj.csrc/gba/renderers/software-obj.c

@@ -212,7 +212,7 @@ if (tileData & 0x8000) { \

renderer->row[outX] |= FLAG_OBJWIN; \ } -int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) { +int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y) { int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0]; int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1]; int start = renderer->start;

@@ -255,6 +255,9 @@ }

} color_t* palette = &renderer->normalPalette[0x100]; + if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) { + palette = &renderer->highlightPalette[0x100]; + } color_t* objwinPalette = palette; if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) {

@@ -269,6 +272,9 @@ }

} } else if (variant) { palette = &renderer->variantPalette[0x100]; + if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) { + palette = &renderer->highlightVariantPalette[0x100]; + } if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) { objwinPalette = palette; }
M src/gba/renderers/software-private.hsrc/gba/renderers/software-private.h

@@ -26,7 +26,7 @@ struct GBAVideoSoftwareBackground* background, int y);

void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y); +int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y); void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority); void GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* renderer, int y);

@@ -145,11 +145,17 @@ int objwinOnly = 0; \

int objwinForceEnable = 0; \ UNUSED(objwinForceEnable); \ color_t* objwinPalette = renderer->normalPalette; \ + if (renderer->d.highlightAmount && background->highlight) { \ + objwinPalette = renderer->highlightPalette; \ + } \ UNUSED(objwinPalette); \ if (objwinSlowPath) { \ if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && \ (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \ objwinPalette = renderer->variantPalette; \ + if (renderer->d.highlightAmount && background->highlight) { \ + palette = renderer->highlightVariantPalette; \ + } \ } \ switch (background->index) { \ case 0: \

@@ -204,8 +210,14 @@ } \

int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \ (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ color_t* palette = renderer->normalPalette; \ + if (renderer->d.highlightAmount && background->highlight) { \ + palette = renderer->highlightPalette; \ + } \ if (variant) { \ palette = renderer->variantPalette; \ + if (renderer->d.highlightAmount && background->highlight) { \ + palette = renderer->highlightVariantPalette; \ + } \ } \ UNUSED(palette); \ PREPARE_OBJWIN;
M src/gba/renderers/video-software.csrc/gba/renderers/video-software.c

@@ -68,6 +68,17 @@ renderer->masterEnd = GBA_VIDEO_HORIZONTAL_PIXELS;

renderer->masterHeight = GBA_VIDEO_VERTICAL_PIXELS; renderer->masterScanlines = VIDEO_VERTICAL_TOTAL_PIXELS; + renderer->d.highlightBG[0] = false; + renderer->d.highlightBG[1] = false; + renderer->d.highlightBG[2] = false; + renderer->d.highlightBG[3] = false; + int i; + for (i = 0; i < 128; ++i) { + renderer->d.highlightOBJ[i] = false; + } + renderer->d.highlightColor = GBA_COLOR_WHITE; + renderer->d.highlightAmount = 0; + renderer->temporaryBuffer = 0; }

@@ -573,10 +584,22 @@

GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y); int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y); softwareRenderer->d.vramOBJ[0] = objVramBase; + if (softwareRenderer->lastHighlightAmount != softwareRenderer->d.highlightAmount) { + softwareRenderer->lastHighlightAmount = softwareRenderer->d.highlightAmount; + if (softwareRenderer->lastHighlightAmount) { + softwareRenderer->blendDirty = true; + } + } + if (softwareRenderer->blendDirty) { _updatePalettes(softwareRenderer); softwareRenderer->blendDirty = false; } + + softwareRenderer->bg[0].highlight = softwareRenderer->d.highlightBG[0]; + softwareRenderer->bg[1].highlight = softwareRenderer->d.highlightBG[1]; + softwareRenderer->bg[2].highlight = softwareRenderer->d.highlightBG[2]; + softwareRenderer->bg[3].highlight = softwareRenderer->d.highlightBG[3]; int w; unsigned priority;

@@ -945,12 +968,12 @@ renderer->end = 0;

if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) { continue; } - if (GBAObjAttributesAIsMosaic(sprite->obj.a)) { + if (GBAObjAttributesAIsMosaic(sprite->obj.a) && mosaicV > 1) { localY = mosaicY; - if (localY < sprite->y) { + if (localY < sprite->y && sprite->y < GBA_VIDEO_VERTICAL_PIXELS) { localY = sprite->y; } - if (localY >= sprite->endY) { + if (localY >= (sprite->endY & 0xFF)) { localY = sprite->endY - 1; } }

@@ -965,7 +988,7 @@ if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed) && !GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt)) {

continue; } - int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, localY); + int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, sprite->index, localY); spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c); } if (renderer->spriteCyclesRemaining <= 0) {

@@ -991,6 +1014,14 @@ }

} else { for (i = 0; i < 512; ++i) { renderer->variantPalette[i] = renderer->normalPalette[i]; + } + } + unsigned highlightAmount = renderer->d.highlightAmount >> 4; + + if (highlightAmount) { + for (i = 0; i < 512; ++i) { + renderer->highlightPalette[i] = _mix(0x10 - highlightAmount, renderer->normalPalette[i], highlightAmount, renderer->d.highlightColor); + renderer->highlightVariantPalette[i] = _mix(0x10 - highlightAmount, renderer->variantPalette[i], highlightAmount, renderer->d.highlightColor); } } }
M src/gba/sio/lockstep.csrc/gba/sio/lockstep.c

@@ -71,7 +71,7 @@ }

bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; - node->d.p->multiplayerControl.slave = node->id > 0; + node->d.p->siocnt = GBASIOMultiplayerSetSlave(node->d.p->siocnt, node->id > 0); mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id); node->event.context = node; node->event.name = "GBA SIO Lockstep";

@@ -99,10 +99,10 @@ case SIO_MULTI:

node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister; node->d.p->rcnt |= 3; ATOMIC_ADD(node->p->attachedMulti, 1); - node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached; + node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached); if (node->id) { node->d.p->rcnt |= 4; - node->d.p->multiplayerControl.slave = 1; + node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt); } break; case SIO_NORMAL_32:

@@ -178,10 +178,10 @@ enum mLockstepPhase transferActive;

ATOMIC_LOAD(transferActive, node->p->d.transferActive); if (value & 0x0080 && transferActive == TRANSFER_IDLE) { - if (!node->id && node->d.p->multiplayerControl.ready) { + if (!node->id && GBASIOMultiplayerIsReady(node->d.p->siocnt)) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id); ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING); - ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->d.attached - 1]); + ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]); bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event); int oldWhen = node->event.when;

@@ -220,37 +220,37 @@ sio->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];

sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2]; sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3]; sio->rcnt |= 1; - sio->multiplayerControl.busy = 0; - sio->multiplayerControl.id = node->id; - if (sio->multiplayerControl.irq) { + sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt); + sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id); + if (GBASIOMultiplayerIsIrq(sio->siocnt)) { GBARaiseIRQ(sio->p, IRQ_SIO, 0); } break; case SIO_NORMAL_8: // TODO - sio->normalControl.start = 0; + sio->siocnt = GBASIONormalClearStart(sio->siocnt); if (node->id) { - sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo; + sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF; } else { node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF; } - if (sio->multiplayerControl.irq) { + if (GBASIONormalIsIrq(sio->siocnt)) { GBARaiseIRQ(sio->p, IRQ_SIO, 0); } break; case SIO_NORMAL_32: // TODO - sio->normalControl.start = 0; + sio->siocnt = GBASIONormalClearStart(sio->siocnt); if (node->id) { - sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo; + sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1]; node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16; } else { node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF; } - if (sio->multiplayerControl.irq) { + if (GBASIONormalIsIrq(sio->siocnt)) { GBARaiseIRQ(sio->p, IRQ_SIO, 0); } break;

@@ -278,7 +278,7 @@ switch (transferActive) {

case TRANSFER_IDLE: // If the master hasn't initiated a transfer, it can keep going. node->nextEvent += LOCKSTEP_INCREMENT; - node->d.p->multiplayerControl.ready = attachedMulti == attached; + node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached); break; case TRANSFER_STARTING: // Start the transfer, but wait for the other GBAs to catch up

@@ -352,11 +352,11 @@ ATOMIC_LOAD(transferActive, node->p->d.transferActive);

ATOMIC_LOAD(attachedMulti, node->p->attachedMulti); ATOMIC_LOAD(attached, node->p->d.attached); - node->d.p->multiplayerControl.ready = attachedMulti == attached; + node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached); bool signal = false; switch (transferActive) { case TRANSFER_IDLE: - if (!node->d.p->multiplayerControl.ready) { + if (!GBASIOMultiplayerIsReady(node->d.p->siocnt)) { node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT); } break;

@@ -376,7 +376,7 @@ node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;

node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; - node->d.p->multiplayerControl.busy = 1; + node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt); break; case SIO_NORMAL_8: node->p->multiRecv[node->id] = 0xFFFF;

@@ -455,7 +455,7 @@ if (address == REG_SIOCNT) {

mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value); value &= 0xFF8B; if (!node->id) { - driver->p->normalControl.si = 1; + driver->p->siocnt = GBASIONormalFillSi(driver->p->siocnt); } if (value & 0x0080 && !node->id) { // Internal shift clock
M src/platform/3ds/main.csrc/platform/3ds/main.c

@@ -104,6 +104,7 @@

static C3D_RenderTarget* upscaleBuffer; static C3D_Tex upscaleBufferTex; static bool interframeBlending = false; +static bool sgbCrop = false; static aptHookCookie cookie; static bool core2;

@@ -382,6 +383,10 @@ int fakeBool;

if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { interframeBlending = fakeBool; } + + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } } static void _gameUnloaded(struct mGUIRunner* runner) {

@@ -437,6 +442,12 @@ core->desiredVideoDimensions(core, &corew, &coreh);

int w = corew; int h = coreh; + if (sgbCrop && w == 256 && h == 224) { + w = GB_VIDEO_HORIZONTAL_PIXELS; + h = GB_VIDEO_VERTICAL_PIXELS; + } + int innerw = w; + int innerh = h; // Get greatest common divisor while (w != 0) { int temp = h % w;

@@ -444,8 +455,8 @@ h = w;

w = temp; } int gcd = h; - unsigned aspectw = corew / gcd; - unsigned aspecth = coreh / gcd; + unsigned aspectw = innerw / gcd; + unsigned aspecth = innerh / gcd; int x = 0; int y = 0;

@@ -517,6 +528,8 @@ ctrAddRectEx(color & 0x7FFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);

} ctrFlushBatch(); + innerw = corew; + innerh = coreh; corew = w; coreh = h; screen_h = 240;

@@ -529,19 +542,20 @@ screen_w = 400;

} ctrSetViewportSize(screen_w, screen_h, true); + float afw, afh; switch (screenMode) { default: return; case SM_AF_TOP: case SM_AF_BOTTOM: - w = screen_w / aspectw; - h = screen_h / aspecth; - if (w * aspecth > screen_h) { - w = aspectw * h; - h = aspecth * h; + afw = screen_w / (float) aspectw; + afh = screen_h / (float) aspecth; + if (afw * aspecth > screen_h) { + w = innerw * afh / gcd; + h = innerh * afh / gcd; } else { - h = aspecth * w; - w = aspectw * w; + h = innerh * afw / gcd; + w = innerw * afw / gcd; } break; case SM_SF_TOP:
M src/platform/psp2/main.csrc/platform/psp2/main.c

@@ -41,7 +41,7 @@ }

static uint32_t _pollInput(const struct mInputMap* map) { SceCtrlData pad; - sceCtrlPeekBufferPositive(0, &pad, 1); + sceCtrlPeekBufferPositiveExt2(0, &pad, 1); int input = mInputMapKeyBits(map, PSP2_INPUT, pad.buttons, 0); if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) {

@@ -127,17 +127,17 @@ .name = "Vita Input",

.id = PSP2_INPUT, .keyNames = (const char*[]) { "Select", - 0, - 0, + "L3", + "R3", "Start", "Up", "Right", "Down", "Left", - "L", - "R", - 0, // L2? - 0, // R2? + "L2", + "R2", + "L1", + "R1", "\1\xC", "\1\xA", "\1\xB",
M src/platform/psp2/psp2-context.csrc/platform/psp2/psp2-context.c

@@ -56,6 +56,7 @@ static vita2d_texture* oldTex;

static vita2d_texture* screenshot; static Thread audioThread; static bool interframeBlending = false; +static bool sgbCrop = false; static struct mSceRotationSource { struct mRotationSource d;

@@ -267,7 +268,7 @@ }

uint16_t mPSP2PollInput(struct mGUIRunner* runner) { SceCtrlData pad; - sceCtrlPeekBufferPositive(0, &pad, 1); + sceCtrlPeekBufferPositiveExt2(0, &pad, 1); int activeKeys = mInputMapKeyBits(&runner->core->inputMap, PSP2_INPUT, pad.buttons, 0); int angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 0, pad.ly);

@@ -313,8 +314,8 @@ mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_UP, GBA_KEY_UP);

mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_DOWN, GBA_KEY_DOWN); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LEFT, GBA_KEY_LEFT); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RIGHT, GBA_KEY_RIGHT); - mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LTRIGGER, GBA_KEY_L); - mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RTRIGGER, GBA_KEY_R); + mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, GBA_KEY_L); + mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, GBA_KEY_R); struct mInputAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 }; mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 0, &desc);

@@ -365,6 +366,10 @@ }

if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) { camera.cam = mode; } + int fakeBool; + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } } void mPSP2LoadROM(struct mGUIRunner* runner) {

@@ -396,6 +401,18 @@

int fakeBool; if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { interframeBlending = fakeBool; + } + + // Backcompat: Old versions of mGBA use an older binding system that has different mappings for L/R + if (!sceKernelIsPSVitaTV()) { + int key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_L2)); + if (key >= 0) { + mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, key); + } + key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_R2)); + if (key >= 0) { + mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, key); + } } MutexInit(&audioContext.mutex);

@@ -461,8 +478,13 @@ }

} int fakeBool; - mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool); - interframeBlending = fakeBool; + if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { + interframeBlending = fakeBool; + } + + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } } void mPSP2Teardown(struct mGUIRunner* runner) {

@@ -507,6 +529,13 @@ default:

vita2d_draw_texture_tint(backdrop, 0, 0, tint); // Fall through case SM_PLAIN: + if (sgbCrop && width == 256 && height == 224) { + w = 768; + h = 672; + scalex = 3; + scaley = 3; + break; + } w = 960 / width; h = 544 / height; if (w * height > 544) {

@@ -521,6 +550,13 @@ }

scaley = scalex; break; case SM_ASPECT: + if (sgbCrop && width == 256 && height == 224) { + w = 967; + h = 846; + scalex = 34.0f / 9.0f; + scaley = scalex; + break; + } w = 960 / aspectw; h = 544 / aspecth; if (w * aspecth > 544) {
M src/platform/python/_builder.pysrc/platform/python/_builder.py

@@ -67,6 +67,18 @@ continue

lines.append(line) ffi.cdef('\n'.join(lines)) +ffi.cdef(""" +struct GBARTC { + int32_t bytesRemaining; + int32_t transferStep; + int32_t bitsRead; + int32_t bits; + int32_t commandActive; + RTCCommandData command; + RTCControl control; + uint8_t time[7]; +};""", packed=True) + preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "lib.h")], universal_newlines=True) lines = []
A src/platform/qt/AssetInfo.cpp

@@ -0,0 +1,40 @@

+/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "AssetInfo.h" + +#include <QFontDatabase> +#include <QHBoxLayout> + +using namespace QGBA; + +AssetInfo::AssetInfo(QWidget* parent) + : QGroupBox(parent) +{ +} + +void AssetInfo::addCustomProperty(const QString& id, const QString& visibleName) { + QHBoxLayout* newLayout = new QHBoxLayout; + newLayout->addWidget(new QLabel(visibleName)); + QLabel* value = new QLabel; + value->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + value->setAlignment(Qt::AlignRight); + newLayout->addWidget(value); + m_customProperties[id] = value; + int index = customLocation(); + static_cast<QBoxLayout*>(layout())->insertLayout(index, newLayout); +} + +void AssetInfo::setCustomProperty(const QString& id, const QVariant& value) { + QLabel* label = m_customProperties[id]; + if (!label) { + return; + } + label->setText(value.toString()); +} + +int AssetInfo::customLocation(const QString&) { + return layout()->count(); +}
A src/platform/qt/AssetInfo.h

@@ -0,0 +1,34 @@

+/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include <QGroupBox> +#include <QHash> +#include <QLabel> +#include <QVariant> + +namespace QGBA { + +class CoreController; + +class AssetInfo : public QGroupBox { +Q_OBJECT + +public: + AssetInfo(QWidget* parent = nullptr); + void addCustomProperty(const QString& id, const QString& visibleName); + +public slots: + void setCustomProperty(const QString& id, const QVariant& value); + +protected: + virtual int customLocation(const QString& id = {}); + +private: + QHash<QString, QLabel*> m_customProperties; +}; + +}
M src/platform/qt/AssetTile.cppsrc/platform/qt/AssetTile.cpp

@@ -22,7 +22,7 @@

using namespace QGBA; AssetTile::AssetTile(QWidget* parent) - : QGroupBox(parent) + : AssetInfo(parent) { m_ui.setupUi(this);

@@ -42,16 +42,8 @@ m_ui.g->setFont(font);

m_ui.b->setFont(font); } -void AssetTile::addCustomProperty(const QString& id, const QString& visibleName) { - QHBoxLayout* newLayout = new QHBoxLayout; - newLayout->addWidget(new QLabel(visibleName)); - QLabel* value = new QLabel; - value->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - value->setAlignment(Qt::AlignRight); - newLayout->addWidget(value); - m_customProperties[id] = value; - int index = layout()->indexOf(m_ui.line); - static_cast<QBoxLayout*>(layout())->insertLayout(index, newLayout); +int AssetTile::customLocation(const QString&) { + return layout()->indexOf(m_ui.line); } void AssetTile::setController(std::shared_ptr<CoreController> controller) {

@@ -149,11 +141,3 @@ m_ui.r->setText(tr("0x%0 (%1)").arg(r, 2, 16, QChar('0')).arg(r, 2, 10, QChar('0')));

m_ui.g->setText(tr("0x%0 (%1)").arg(g, 2, 16, QChar('0')).arg(g, 2, 10, QChar('0'))); m_ui.b->setText(tr("0x%0 (%1)").arg(b, 2, 16, QChar('0')).arg(b, 2, 10, QChar('0'))); } - -void AssetTile::setCustomProperty(const QString& id, const QVariant& value) { - QLabel* label = m_customProperties[id]; - if (!label) { - return; - } - label->setText(value.toString()); -}
M src/platform/qt/AssetTile.hsrc/platform/qt/AssetTile.h

@@ -15,13 +15,12 @@ namespace QGBA {

class CoreController; -class AssetTile : public QGroupBox { +class AssetTile : public AssetInfo { Q_OBJECT public: AssetTile(QWidget* parent = nullptr); void setController(std::shared_ptr<CoreController>); - void addCustomProperty(const QString& id, const QString& visibleName); public slots: void setPalette(int);

@@ -29,7 +28,9 @@ void setBoundary(int boundary, int set0, int set1);

void selectIndex(int); void setFlip(bool h, bool v); void selectColor(int); - void setCustomProperty(const QString& id, const QVariant& value); + +protected: + int customLocation(const QString& id = {}) override; private: Ui::AssetTile m_ui;
M src/platform/qt/AssetTile.uisrc/platform/qt/AssetTile.ui

@@ -1,13 +1,13 @@

<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>AssetTile</class> - <widget class="QGroupBox" name="AssetTile"> + <widget class="QGBA::AssetInfo" name="AssetTile"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>171</width> - <height>355</height> + <width>241</width> + <height>406</height> </rect> </property> <property name="sizePolicy">

@@ -185,6 +185,12 @@ </item>

</layout> </widget> <customwidgets> + <customwidget> + <class>QGBA::AssetInfo</class> + <extends>QGroupBox</extends> + <header>AssetInfo.h</header> + <container>1</container> + </customwidget> <customwidget> <class>QGBA::Swatch</class> <extends>QWidget</extends>
M src/platform/qt/AssetView.cppsrc/platform/qt/AssetView.cpp

@@ -1,4 +1,4 @@

-/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2019 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

@@ -8,6 +8,16 @@

#include "CoreController.h" #include <QTimer> + +#ifdef M_CORE_GBA +#include <mgba/internal/gba/gba.h> +#endif +#ifdef M_CORE_GB +#include <mgba/internal/gb/gb.h> +#include <mgba/internal/gb/io.h> +#endif + +#include <mgba/core/map-cache.h> using namespace QGBA;

@@ -98,3 +108,175 @@ }

break; } } + +QImage AssetView::compositeMap(int map, mMapCacheEntry* mapStatus) { + mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, map); + int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig); + int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig); + QImage rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32); + uchar* bgBits = rawMap.bits(); + for (int j = 0; j < tilesH; ++j) { + for (int i = 0; i < tilesW; ++i) { + mMapCacheCleanTile(mapCache, mapStatus, i, j); + } + for (int i = 0; i < 8; ++i) { + memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32); + } + } + return rawMap.rgbSwapped(); +} + +QImage AssetView::compositeObj(const ObjInfo& objInfo) { + mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, objInfo.paletteSet); + const color_t* rawPalette = mTileCacheGetPalette(tileCache, objInfo.paletteId); + unsigned colors = 1 << objInfo.bits; + QVector<QRgb> palette; + + palette.append(rawPalette[0] & 0xFFFFFF); + for (unsigned c = 1; c < colors && c < 256; ++c) { + palette.append(rawPalette[c] | 0xFF000000); + } + + QImage image = QImage(QSize(objInfo.width * 8, objInfo.height * 8), QImage::Format_Indexed8); + image.setColorTable(palette); + uchar* bits = image.bits(); + unsigned t = objInfo.tile; + for (int y = 0; y < objInfo.height; ++y) { + for (int x = 0; x < objInfo.width; ++x, ++t) { + compositeTile(static_cast<const void*>(mTileCacheGetVRAM(tileCache, t)), bits, objInfo.width * 8, x * 8, y * 8, objInfo.bits); + } + t += objInfo.stride - objInfo.width; + } + return image.rgbSwapped(); +} + +bool AssetView::lookupObj(int id, struct ObjInfo* info) { + switch (m_controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + return lookupObjGBA(id, info); +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + return lookupObjGB(id, info); +#endif + default: + return false; + } +} + +#ifdef M_CORE_GBA +bool AssetView::lookupObjGBA(int id, struct ObjInfo* info) { + if (id > 127) { + return false; + } + + const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board); + const GBAObj* obj = &gba->video.oam.obj[id]; + + unsigned shape = GBAObjAttributesAGetShape(obj->a); + unsigned size = GBAObjAttributesBGetSize(obj->b); + unsigned width = GBAVideoObjSizes[shape * 4 + size][0]; + unsigned height = GBAVideoObjSizes[shape * 4 + size][1]; + unsigned tile = GBAObjAttributesCGetTile(obj->c); + unsigned palette = GBAObjAttributesCGetPalette(obj->c); + unsigned tileBase = tile; + unsigned paletteSet; + unsigned bits; + if (GBAObjAttributesAIs256Color(obj->a)) { + paletteSet = 3; + palette = 0; + tile /= 2; + bits = 8; + } else { + paletteSet = 2; + bits = 4; + } + ObjInfo newInfo{ + tile, + width / 8, + height / 8, + width / 8, + palette, + paletteSet, + bits, + !GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a), + GBAObjAttributesCGetPriority(obj->c), + GBAObjAttributesBGetX(obj->b), + GBAObjAttributesAGetY(obj->a), + false, + false, + }; + if (GBAObjAttributesAIsTransformed(obj->a)) { + int matIndex = GBAObjAttributesBGetMatIndex(obj->b); + const GBAOAMMatrix* mat = &gba->video.oam.mat[matIndex]; + QTransform invXform(mat->a / 256., mat->c / 256., mat->b / 256., mat->d / 256., 0, 0); + newInfo.xform = invXform.inverted(); + } else { + newInfo.hflip = bool(GBAObjAttributesBIsHFlip(obj->b)); + newInfo.vflip = bool(GBAObjAttributesBIsVFlip(obj->b)); + } + GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues + if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) { + newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a)); + }; + *info = newInfo; + return true; +} +#endif + +#ifdef M_CORE_GB +bool AssetView::lookupObjGB(int id, struct ObjInfo* info) { + if (id > 39) { + return false; + } + + const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board); + const GBObj* obj = &gb->video.oam.obj[id]; + + unsigned width = 8; + unsigned height = 8; + GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC]; + if (GBRegisterLCDCIsObjSize(lcdc)) { + height = 16; + } + unsigned tile = obj->tile; + unsigned palette = 0; + if (gb->model >= GB_MODEL_CGB) { + if (GBObjAttributesIsBank(obj->attr)) { + tile += 512; + } + palette = GBObjAttributesGetCGBPalette(obj->attr); + } else { + palette = GBObjAttributesGetPalette(obj->attr); + } + palette += 8; + + ObjInfo newInfo{ + tile, + 1, + height / 8, + 1, + palette, + 0, + 2, + obj->y != 0 && obj->y < 160 && obj->x != 0 && obj->x < 168, + GBObjAttributesGetPriority(obj->attr), + obj->x - 8, + obj->y - 16, + bool(GBObjAttributesIsXFlip(obj->attr)), + bool(GBObjAttributesIsYFlip(obj->attr)), + }; + *info = newInfo; + return true; +} +#endif + +bool AssetView::ObjInfo::operator!=(const ObjInfo& other) const { + return other.tile != tile || + other.width != width || + other.height != height || + other.stride != stride || + other.paletteId != paletteId || + other.paletteSet != paletteSet; +}
M src/platform/qt/AssetView.hsrc/platform/qt/AssetView.h

@@ -6,11 +6,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#pragma once #include <QTimer> +#include <QTransform> #include <QWidget> #include <mgba/core/cache-set.h> #include <memory> + +struct mMapCacheEntry; namespace QGBA {

@@ -22,8 +25,6 @@

public: AssetView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); - static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8); - protected slots: void updateTiles(); void updateTiles(bool force);

@@ -40,9 +41,43 @@ void resizeEvent(QResizeEvent*) override;

void showEvent(QShowEvent*) override; mCacheSet* const m_cacheSet; + std::shared_ptr<CoreController> m_controller; + +protected: + struct ObjInfo { + unsigned tile; + unsigned width; + unsigned height; + unsigned stride; + unsigned paletteId; + unsigned paletteSet; + unsigned bits; + + bool enabled : 1; + unsigned priority : 2; + int x : 10; + int y : 10; + bool hflip : 1; + bool vflip : 1; + QTransform xform; + + bool operator!=(const ObjInfo&) const; + }; + + static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8); + QImage compositeMap(int map, mMapCacheEntry*); + QImage compositeObj(const ObjInfo&); + + bool lookupObj(int id, struct ObjInfo*); private: - std::shared_ptr<CoreController> m_controller; +#ifdef M_CORE_GBA + bool lookupObjGBA(int id, struct ObjInfo*); +#endif +#ifdef M_CORE_GB + bool lookupObjGB(int id, struct ObjInfo*); +#endif + QTimer m_updateTimer; };
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -60,6 +60,7 @@ AboutScreen.cpp

AbstractUpdater.cpp Action.cpp ActionMapper.cpp + AssetInfo.cpp AssetTile.cpp AssetView.cpp AudioProcessor.cpp

@@ -72,6 +73,7 @@ CoreController.cpp

Display.cpp DisplayGL.cpp DisplayQt.cpp + FrameView.cpp GBAApp.cpp GIFView.cpp GamepadAxisEvent.cpp

@@ -124,6 +126,7 @@ AssetTile.ui

BattleChipView.ui CheatsView.ui DebuggerConsole.ui + FrameView.ui GIFView.ui IOViewer.ui LoadSaveState.ui
M src/platform/qt/ColorPicker.cppsrc/platform/qt/ColorPicker.cpp

@@ -34,6 +34,14 @@

return *this; } +void ColorPicker::setColor(const QColor& color) { + m_defaultColor = color; + + QPalette palette = m_parent->palette(); + palette.setColor(m_parent->backgroundRole(), color); + m_parent->setPalette(palette); +} + bool ColorPicker::eventFilter(QObject* obj, QEvent* event) { if (event->type() != QEvent::MouseButtonRelease) { return false;
M src/platform/qt/ColorPicker.hsrc/platform/qt/ColorPicker.h

@@ -24,6 +24,9 @@

signals: void colorChanged(const QColor&); +public slots: + void setColor(const QColor&); + protected: bool eventFilter(QObject* obj, QEvent* event) override;
M src/platform/qt/CoreController.cppsrc/platform/qt/CoreController.cpp

@@ -82,8 +82,7 @@

controller->m_resetActions.clear(); if (!controller->m_hwaccel) { - controller->m_activeBuffer = &controller->m_buffers[0]; - context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer->data()), controller->screenDimensions().width()); + context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer.data()), controller->screenDimensions().width()); } QMetaObject::invokeMethod(controller, "didReset");

@@ -203,13 +202,33 @@ m_threadContext.core->deinit(m_threadContext.core);

} const color_t* CoreController::drawContext() { - QMutexLocker locker(&m_mutex); if (m_hwaccel) { return nullptr; } + QMutexLocker locker(&m_bufferMutex); return reinterpret_cast<const color_t*>(m_completeBuffer.constData()); } +QImage CoreController::getPixels() { + QByteArray buffer; + QSize size = screenDimensions(); + size_t stride = size.width() * BYTES_PER_PIXEL; + + if (!m_hwaccel) { + buffer = m_completeBuffer; + } else { + Interrupter interrupter(this); + const void* pixels; + m_threadContext.core->getPixels(m_threadContext.core, &pixels, &stride); + stride *= BYTES_PER_PIXEL; + buffer.resize(stride * size.height()); + memcpy(buffer.data(), pixels, buffer.size()); + } + + return QImage(reinterpret_cast<const uchar*>(buffer.constData()), + size.width(), size.height(), stride, QImage::Format_RGBX8888); +} + bool CoreController::isPaused() { return mCoreThreadIsPaused(&m_threadContext); }

@@ -342,15 +361,12 @@ }

void CoreController::start() { if (!m_hwaccel) { - QSize size(1024, 2048); - m_buffers[0].resize(size.width() * size.height() * sizeof(color_t)); - m_buffers[1].resize(size.width() * size.height() * sizeof(color_t)); - m_buffers[0].fill(0xFF); - m_buffers[1].fill(0xFF); - m_activeBuffer = &m_buffers[0]; - m_completeBuffer = m_buffers[0]; + QSize size(256, 384); + m_activeBuffer.resize(size.width() * size.height() * sizeof(color_t)); + m_activeBuffer.fill(0xFF); + m_completeBuffer = m_activeBuffer; - m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), size.width()); + m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), size.width()); } if (!m_patched) {

@@ -386,8 +402,7 @@ if (paused == isPaused()) {

return; } if (paused) { - QMutexLocker locker(&m_mutex); - m_frameActions.append([this]() { + addFrameAction([this]() { mCoreThreadPauseFromThread(&m_threadContext); }); } else {

@@ -396,13 +411,17 @@ }

} void CoreController::frameAdvance() { - QMutexLocker locker(&m_mutex); - m_frameActions.append([this]() { + addFrameAction([this]() { mCoreThreadPauseFromThread(&m_threadContext); }); setPaused(false); } +void CoreController::addFrameAction(std::function<void ()> action) { + QMutexLocker locker(&m_actionMutex); + m_frameActions.append(action); +} + void CoreController::setSync(bool sync) { if (sync) { m_threadContext.impl->sync.audioWait = m_audioSync;

@@ -777,26 +796,39 @@ void CoreController::clearOverride() {

m_override.reset(); } -void CoreController::startVideoLog(const QString& path) { +void CoreController::startVideoLog(const QString& path, bool compression) { if (m_vl) { return; } + VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return; + } + startVideoLog(vf); +} + +void CoreController::startVideoLog(VFile* vf, bool compression) { + if (m_vl || !vf) { + return; + } + Interrupter interrupter(this); m_vl = mVideoLogContextCreate(m_threadContext.core); - m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + m_vlVf = vf; mVideoLogContextSetOutput(m_vl, m_vlVf); + mVideoLogContextSetCompression(m_vl, compression); mVideoLogContextWriteHeader(m_vl, m_threadContext.core); } -void CoreController::endVideoLog() { +void CoreController::endVideoLog(bool closeVf) { if (!m_vl) { return; } Interrupter interrupter(this); mVideoLogContextDestroy(m_threadContext.core, m_vl); - if (m_vlVf) { + if (m_vlVf && closeVf) { m_vlVf->close(m_vlVf); m_vlVf = nullptr; }

@@ -819,23 +851,20 @@ m_threadContext.core->setKeys(m_threadContext.core, activeKeys);

} void CoreController::finishFrame() { - QMutexLocker locker(&m_mutex); if (!m_hwaccel) { - memcpy(m_completeBuffer.data(), m_activeBuffer->constData(), m_activeBuffer->size()); + unsigned width, height; + m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); - // TODO: Generalize this to triple buffering? - m_activeBuffer = &m_buffers[0]; - if (m_activeBuffer == m_completeBuffer) { - m_activeBuffer = &m_buffers[1]; - } - // Copy contents to avoid issues when doing frameskip - memcpy(m_activeBuffer->data(), m_completeBuffer.constData(), m_activeBuffer->size()); - m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), screenDimensions().width()); + QMutexLocker locker(&m_bufferMutex); + memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), 256 * height * BYTES_PER_PIXEL); } - for (auto& action : m_frameActions) { + + QMutexLocker locker(&m_actionMutex); + QList<std::function<void ()>> frameActions(m_frameActions); + m_frameActions.clear(); + for (auto& action : frameActions) { action(); } - m_frameActions.clear(); updateKeys(); QMetaObject::invokeMethod(this, "frameAvailable");
M src/platform/qt/CoreController.hsrc/platform/qt/CoreController.h

@@ -67,6 +67,7 @@

mCoreThread* thread() { return &m_threadContext; } const color_t* drawContext(); + QImage getPixels(); bool isPaused(); bool hasStarted();

@@ -101,6 +102,8 @@ void setLogger(LogController*);

bool audioSync() const { return m_audioSync; } bool videoSync() const { return m_videoSync; } + + void addFrameAction(std::function<void ()> callback); public slots: void start();

@@ -157,8 +160,9 @@ void clearAVStream();

void clearOverride(); - void startVideoLog(const QString& path); - void endVideoLog(); + void startVideoLog(const QString& path, bool compression = true); + void startVideoLog(VFile* vf, bool compression = true); + void endVideoLog(bool closeVf = true); void setFramebufferHandle(int fb);

@@ -193,8 +197,7 @@ mCoreThread m_threadContext{};

bool m_patched = false; - QByteArray m_buffers[2]; - QByteArray* m_activeBuffer; + QByteArray m_activeBuffer; QByteArray m_completeBuffer; bool m_hwaccel = false;

@@ -203,7 +206,8 @@ std::unique_ptr<Override> m_override;

QList<std::function<void()>> m_resetActions; QList<std::function<void()>> m_frameActions; - QMutex m_mutex; + QMutex m_actionMutex{QMutex::Recursive}; + QMutex m_bufferMutex; int m_activeKeys = 0; bool m_autofire[32] = {};
M src/platform/qt/DisplayGL.cppsrc/platform/qt/DisplayGL.cpp

@@ -114,7 +114,6 @@ #else

messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio()); #endif resizePainter(); - connect(m_context.get(), &CoreController::didReset, this, &DisplayGL::resizeContext); } void DisplayGL::stopDrawing() {
A src/platform/qt/FrameView.cpp

@@ -0,0 +1,467 @@

+/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "FrameView.h" + +#include <QMouseEvent> +#include <QPalette> + +#include <array> +#include <cmath> + +#include "CoreController.h" +#include "GBAApp.h" + +#include <mgba/core/core.h> +#include <mgba/feature/video-logger.h> +#ifdef M_CORE_GBA +#include <mgba/internal/gba/gba.h> +#include <mgba/internal/gba/io.h> +#include <mgba/internal/gba/memory.h> +#include <mgba/internal/gba/video.h> +#endif +#ifdef M_CORE_GB +#include <mgba/internal/gb/gb.h> +#include <mgba/internal/gb/memory.h> +#endif + +using namespace QGBA; + +FrameView::FrameView(std::shared_ptr<CoreController> controller, QWidget* parent) + : AssetView(controller, parent) +{ + m_ui.setupUi(this); + + m_glowTimer.setInterval(33); + connect(&m_glowTimer, &QTimer::timeout, this, [this]() { + ++m_glowFrame; + invalidateQueue(); + }); + + m_ui.compositedView->installEventFilter(this); + + connect(m_ui.queue, &QListWidget::itemChanged, this, [this](QListWidgetItem* item) { + Layer& layer = m_queue[item->data(Qt::UserRole).toInt()]; + layer.enabled = item->checkState() == Qt::Checked; + if (layer.enabled) { + m_disabled.remove(layer.id); + } else { + m_disabled.insert(layer.id); + } + invalidateQueue(); + }); + connect(m_ui.queue, &QListWidget::currentItemChanged, this, [this](QListWidgetItem* item) { + if (item) { + m_active = m_queue[item->data(Qt::UserRole).toInt()].id; + } else { + m_active = {}; + } + invalidateQueue(); + }); + connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() { + invalidateQueue(); + }); + connect(m_ui.exportButton, &QAbstractButton::pressed, this, &FrameView::exportFrame); + connect(m_ui.reset, &QAbstractButton::pressed, this, &FrameView::reset); + + m_backdropPicker = ColorPicker(m_ui.backdrop, QColor(0, 0, 0, 0)); + connect(&m_backdropPicker, &ColorPicker::colorChanged, this, [this](const QColor& color) { + m_overrideBackdrop = color; + }); + m_controller->addFrameAction(std::bind(&FrameView::frameCallback, this, m_callbackLocker)); + + { + CoreController::Interrupter interrupter(m_controller); + refreshVl(); + } + m_controller->frameAdvance(); +} + +FrameView::~FrameView() { + QMutexLocker locker(&m_mutex); + *m_callbackLocker = false; + if (m_vl) { + m_vl->deinit(m_vl); + } +} + +bool FrameView::lookupLayer(const QPointF& coord, Layer*& out) { + for (Layer& layer : m_queue) { + if (!layer.enabled || m_disabled.contains(layer.id)) { + continue; + } + QPointF location = layer.location; + QSizeF layerDims(layer.image.width(), layer.image.height()); + QRegion region; + if (layer.repeats) { + if (location.x() + layerDims.width() < 0) { + location.setX(std::fmod(location.x(), layerDims.width())); + } + if (location.y() + layerDims.height() < 0) { + location.setY(std::fmod(location.y(), layerDims.height())); + } + + region += layer.mask.translated(location.x(), location.y()); + region += layer.mask.translated(location.x() + layerDims.width(), location.y()); + region += layer.mask.translated(location.x(), location.y() + layerDims.height()); + region += layer.mask.translated(location.x() + layerDims.width(), location.y() + layerDims.height()); + } else { + region = layer.mask.translated(location.x(), location.y()); + } + + if (region.contains(QPoint(coord.x(), coord.y()))) { + out = &layer; + return true; + } + } + return false; +} + +void FrameView::selectLayer(const QPointF& coord) { + Layer* layer; + if (!lookupLayer(coord, layer)) { + return; + } + if (layer->id == m_active) { + m_active = {}; + } else { + m_active = layer->id; + } + m_glowFrame = 0; +} + +void FrameView::disableLayer(const QPointF& coord) { + Layer* layer; + if (!lookupLayer(coord, layer)) { + return; + } + layer->enabled = false; + m_disabled.insert(layer->id); +} + +#ifdef M_CORE_GBA +void FrameView::updateTilesGBA(bool force) { + if (m_ui.freeze->checkState() == Qt::Checked) { + return; + } + QMutexLocker locker(&m_mutex); + m_queue.clear(); + { + CoreController::Interrupter interrupter(m_controller); + + uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io; + QRgb backdrop = M_RGB5_TO_RGB8(static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[0]); + m_gbaDispcnt = io[REG_DISPCNT >> 1]; + int mode = GBARegisterDISPCNTGetMode(m_gbaDispcnt); + + std::array<bool, 4> enabled{ + bool(GBARegisterDISPCNTIsBg0Enable(m_gbaDispcnt)), + bool(GBARegisterDISPCNTIsBg1Enable(m_gbaDispcnt)), + bool(GBARegisterDISPCNTIsBg2Enable(m_gbaDispcnt)), + bool(GBARegisterDISPCNTIsBg3Enable(m_gbaDispcnt)), + }; + + for (int priority = 0; priority < 4; ++priority) { + for (int sprite = 0; sprite < 128; ++sprite) { + ObjInfo info; + lookupObj(sprite, &info); + + if (!info.enabled || info.priority != priority) { + continue; + } + + QPointF offset(info.x, info.y); + QImage obj(compositeObj(info)); + if (info.hflip || info.vflip) { + obj = obj.mirrored(info.hflip, info.vflip); + } + if (!info.xform.isIdentity()) { + offset += QPointF(obj.width(), obj.height()) / 2; + obj = obj.transformed(info.xform); + offset -= QPointF(obj.width() / 2, obj.height() / 2); + } + m_queue.append({ + { LayerId::SPRITE, sprite }, + !m_disabled.contains({ LayerId::SPRITE, sprite }), + QPixmap::fromImage(obj), + {}, offset, false + }); + if (m_queue.back().image.hasAlpha()) { + m_queue.back().mask = QRegion(m_queue.back().image.mask()); + } else { + m_queue.back().mask = QRegion(0, 0, m_queue.back().image.width(), m_queue.back().image.height()); + } + } + + for (int bg = 0; bg < 4; ++bg) { + if (!enabled[bg]) { + continue; + } + if (GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + bg]) != priority) { + continue; + } + + QPointF offset; + if (mode == 0) { + offset.setX(-(io[(REG_BG0HOFS >> 1) + (bg << 1)] & 0x1FF)); + offset.setY(-(io[(REG_BG0VOFS >> 1) + (bg << 1)] & 0x1FF)); + }; + m_queue.append({ + { LayerId::BACKGROUND, bg }, + !m_disabled.contains({ LayerId::BACKGROUND, bg }), + QPixmap::fromImage(compositeMap(bg, m_mapStatus[bg])), + {}, offset, true + }); + if (m_queue.back().image.hasAlpha()) { + m_queue.back().mask = QRegion(m_queue.back().image.mask()); + } else { + m_queue.back().mask = QRegion(0, 0, m_queue.back().image.width(), m_queue.back().image.height()); + } + } + } + QImage backdropImage(QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS), QImage::Format_Mono); + backdropImage.fill(1); + backdropImage.setColorTable({backdrop, backdrop | 0xFF000000 }); + m_queue.append({ + { LayerId::BACKDROP }, + !m_disabled.contains({ LayerId::BACKDROP }), + QPixmap::fromImage(backdropImage), + {}, {0, 0}, false + }); + updateRendered(); + } + invalidateQueue(QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS)); +} + +void FrameView::injectGBA() { + mVideoLogger* logger = m_vl->videoLogger; + mVideoLoggerInjectionPoint(logger, LOGGER_INJECTION_FIRST_SCANLINE); + GBA* gba = static_cast<GBA*>(m_vl->board); + gba->video.renderer->highlightBG[0] = false; + gba->video.renderer->highlightBG[1] = false; + gba->video.renderer->highlightBG[2] = false; + gba->video.renderer->highlightBG[3] = false; + for (int i = 0; i < 128; ++i) { + gba->video.renderer->highlightOBJ[i] = false; + } + QPalette palette; + gba->video.renderer->highlightColor = palette.color(QPalette::HighlightedText).rgb(); + gba->video.renderer->highlightAmount = sin(m_glowFrame * M_PI / 30) * 48 + 64; + if (!m_overrideBackdrop.isValid()) { + QRgb backdrop = M_RGB5_TO_RGB8(gba->video.palette[0]) | 0xFF000000; + m_backdropPicker.setColor(backdrop); + } + + m_vl->reset(m_vl); + for (const Layer& layer : m_queue) { + switch (layer.id.type) { + case LayerId::SPRITE: + if (!layer.enabled) { + mVideoLoggerInjectOAM(logger, layer.id.index << 2, 0x200); + } + if (layer.id == m_active) { + gba->video.renderer->highlightOBJ[layer.id.index] = true; + } + break; + case LayerId::BACKGROUND: + m_vl->enableVideoLayer(m_vl, layer.id.index, layer.enabled); + if (layer.id == m_active) { + gba->video.renderer->highlightBG[layer.id.index] = true; + } + break; + } + } + if (m_overrideBackdrop.isValid()) { + mVideoLoggerInjectPalette(logger, 0, M_RGB8_TO_RGB5(m_overrideBackdrop.rgb())); + } + if (m_ui.disableScanline->checkState() == Qt::Checked) { + mVideoLoggerIgnoreAfterInjection(logger, (1 << DIRTY_PALETTE) | (1 << DIRTY_OAM) | (1 << DIRTY_REGISTER)); + } else { + mVideoLoggerIgnoreAfterInjection(logger, 0); + } +} +#endif + +#ifdef M_CORE_GB +void FrameView::updateTilesGB(bool force) { + if (m_ui.freeze->checkState() == Qt::Checked) { + return; + } + m_queue.clear(); + { + CoreController::Interrupter interrupter(m_controller); + updateRendered(); + } + invalidateQueue(m_controller->screenDimensions()); +} + +void FrameView::injectGB() { + for (const Layer& layer : m_queue) { + } +} +#endif + +void FrameView::invalidateQueue(const QSize& dims) { + if (dims.isValid()) { + m_dims = dims; + } + bool blockSignals = m_ui.queue->blockSignals(true); + QMutexLocker locker(&m_mutex); + if (m_vl) { + switch (m_controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + injectGBA(); +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + injectGB(); +#endif + } + m_vl->runFrame(m_vl); + } + + for (int i = 0; i < m_queue.count(); ++i) { + const Layer& layer = m_queue[i]; + QListWidgetItem* item; + if (i >= m_ui.queue->count()) { + item = new QListWidgetItem; + m_ui.queue->addItem(item); + } else { + item = m_ui.queue->item(i); + } + item->setText(layer.id.readable()); + item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); + item->setCheckState(layer.enabled ? Qt::Checked : Qt::Unchecked); + item->setData(Qt::UserRole, i); + item->setSelected(layer.id == m_active); + } + + while (m_ui.queue->count() > m_queue.count()) { + delete m_ui.queue->takeItem(m_queue.count()); + } + m_ui.queue->blockSignals(blockSignals); + + QPixmap composited; + if (m_framebuffer.isNull()) { + updateRendered(); + composited = m_rendered; + } else { + m_ui.exportButton->setEnabled(true); + composited.convertFromImage(m_framebuffer); + } + m_composited = composited.scaled(m_dims * m_ui.magnification->value()); + m_ui.compositedView->setPixmap(m_composited); +} + +void FrameView::updateRendered() { + if (m_ui.freeze->checkState() == Qt::Checked) { + return; + } + m_rendered.convertFromImage(m_controller->getPixels()); +} + +bool FrameView::eventFilter(QObject* obj, QEvent* event) { + QPointF pos; + switch (event->type()) { + case QEvent::MouseButtonPress: + pos = static_cast<QMouseEvent*>(event)->localPos(); + pos /= m_ui.magnification->value(); + selectLayer(pos); + return true; + case QEvent::MouseButtonDblClick: + pos = static_cast<QMouseEvent*>(event)->localPos(); + pos /= m_ui.magnification->value(); + disableLayer(pos); + return true; + } + return false; +} + +void FrameView::refreshVl() { + QMutexLocker locker(&m_mutex); + m_currentFrame = m_nextFrame; + m_nextFrame = VFileMemChunk(nullptr, 0); + if (m_currentFrame) { + m_controller->endVideoLog(false); + VFile* currentFrame = VFileMemChunk(nullptr, m_currentFrame->size(m_currentFrame)); + void* buffer = currentFrame->map(currentFrame, m_currentFrame->size(m_currentFrame), MAP_WRITE); + m_currentFrame->seek(m_currentFrame, 0, SEEK_SET); + m_currentFrame->read(m_currentFrame, buffer, m_currentFrame->size(m_currentFrame)); + currentFrame->unmap(currentFrame, buffer, m_currentFrame->size(m_currentFrame)); + m_currentFrame = currentFrame; + QMetaObject::invokeMethod(this, "newVl"); + } + m_controller->endVideoLog(); + m_controller->startVideoLog(m_nextFrame, false); +} + +void FrameView::newVl() { + if (!m_glowTimer.isActive()) { + m_glowTimer.start(); + } + QMutexLocker locker(&m_mutex); + if (m_vl) { + m_vl->deinit(m_vl); + } + m_vl = mCoreFindVF(m_currentFrame); + m_vl->init(m_vl); + m_vl->loadROM(m_vl, m_currentFrame); + mCoreInitConfig(m_vl, nullptr); + unsigned width, height; + m_vl->desiredVideoDimensions(m_vl, &width, &height); + m_framebuffer = QImage(width, height, QImage::Format_RGBX8888); + m_vl->setVideoBuffer(m_vl, reinterpret_cast<color_t*>(m_framebuffer.bits()), width); + m_vl->reset(m_vl); +} + +void FrameView::frameCallback(FrameView* viewer, std::shared_ptr<bool> lock) { + if (!*lock) { + return; + } + CoreController::Interrupter interrupter(viewer->m_controller, true); + viewer->refreshVl(); + viewer->m_controller->addFrameAction(std::bind(&FrameView::frameCallback, viewer, lock)); +} + +void FrameView::exportFrame() { + QString filename = GBAApp::app()->getSaveFileName(this, tr("Export frame"), + tr("Portable Network Graphics (*.png)")); + CoreController::Interrupter interrupter(m_controller); + m_framebuffer.save(filename, "PNG"); +} + +void FrameView::reset() { + m_disabled.clear(); + for (Layer& layer : m_queue) { + layer.enabled = true; + } + m_overrideBackdrop = QColor(); + invalidateQueue(); +} + +QString FrameView::LayerId::readable() const { + QString typeStr; + switch (type) { + case NONE: + return tr("None"); + case BACKGROUND: + typeStr = tr("Background"); + break; + case WINDOW: + typeStr = tr("Window"); + break; + case SPRITE: + typeStr = tr("Sprite"); + break; + case BACKDROP: + typeStr = tr("Backdrop"); + break; + } + if (index < 0) { + return typeStr; + } + return tr("%1 %2").arg(typeStr).arg(index); +}
A src/platform/qt/FrameView.h

@@ -0,0 +1,120 @@

+/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include "ui_FrameView.h" + +#include <QBitmap> +#include <QImage> +#include <QList> +#include <QMutex> +#include <QPixmap> +#include <QSet> +#include <QTimer> + +#include "AssetView.h" +#include "ColorPicker.h" + +#include <mgba-util/vfs.h> + +#include <memory> + +struct VFile; + +namespace QGBA { + +class CoreController; + +class FrameView : public AssetView { +Q_OBJECT + +public: + FrameView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); + ~FrameView(); + +public slots: + void selectLayer(const QPointF& coord); + void disableLayer(const QPointF& coord); + void exportFrame(); + void reset(); + +protected: +#ifdef M_CORE_GBA + void updateTilesGBA(bool force) override; + void injectGBA(); +#endif +#ifdef M_CORE_GB + void updateTilesGB(bool force) override; + void injectGB(); +#endif + + bool eventFilter(QObject* obj, QEvent* event) override; + +private slots: + void invalidateQueue(const QSize& = {}); + void updateRendered(); + void refreshVl(); + void newVl(); + +private: + struct LayerId { + enum { + NONE = 0, + BACKGROUND, + WINDOW, + SPRITE, + BACKDROP + } type = NONE; + int index = -1; + + bool operator!=(const LayerId& other) const { return other.type != type || other.index != index; } + operator uint() const { return (type << 8) | index; } + QString readable() const; + }; + + struct Layer { + LayerId id; + bool enabled; + QPixmap image; + QRegion mask; + QPointF location; + bool repeats; + }; + + bool lookupLayer(const QPointF& coord, Layer*&); + + static void frameCallback(FrameView*, std::shared_ptr<bool>); + + Ui::FrameView m_ui; + + LayerId m_active{}; + + int m_glowFrame; + QTimer m_glowTimer; + + QMutex m_mutex{QMutex::Recursive}; + VFile* m_currentFrame = nullptr; + VFile* m_nextFrame = nullptr; + mCore* m_vl = nullptr; + QImage m_framebuffer; + + QSize m_dims; + QList<Layer> m_queue; + QSet<LayerId> m_disabled; + QPixmap m_composited; + QPixmap m_rendered; + mMapCacheEntry m_mapStatus[4][128 * 128] = {}; // TODO: Correct size + ColorPicker m_backdropPicker; + QColor m_overrideBackdrop; + +#ifdef M_CORE_GBA + uint16_t m_gbaDispcnt; +#endif + + std::shared_ptr<bool> m_callbackLocker{std::make_shared<bool>(true)}; +}; + +}
A src/platform/qt/FrameView.ui

@@ -0,0 +1,160 @@

+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>FrameView</class> + <widget class="QWidget" name="FrameView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>869</width> + <height>875</height> + </rect> + </property> + <property name="windowTitle"> + <string>Inspect frame</string> + </property> + <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,1,0,0,0" columnstretch="0,1"> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QSpinBox" name="magnification"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="suffix"> + <string>×</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>8</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Magnification</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="freeze"> + <property name="text"> + <string>Freeze frame</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QFrame" name="backdrop"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Backdrop color</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1" rowspan="9"> + <widget class="QScrollArea" name="compositedArea"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents_2"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>567</width> + <height>382</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="compositedView"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="disableScanline"> + <property name="text"> + <string>Disable scanline effects</string> + </property> + </widget> + </item> + <item row="5" column="0" rowspan="2"> + <widget class="QListWidget" name="queue"/> + </item> + <item row="8" column="0"> + <widget class="QPushButton" name="exportButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Export</string> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QPushButton" name="reset"> + <property name="text"> + <string>Reset</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui>
M src/platform/qt/MapView.cppsrc/platform/qt/MapView.cpp

@@ -18,6 +18,7 @@ #include <mgba/internal/gba/memory.h>

#include <mgba/internal/gba/video.h> #endif #ifdef M_CORE_GB +#include <mgba/internal/gb/gb.h> #include <mgba/internal/gb/memory.h> #endif

@@ -42,6 +43,12 @@ case PLATFORM_GBA:

m_boundary = 2048; m_addressBase = BASE_VRAM; m_addressWidth = 8; + m_ui.bgInfo->addCustomProperty("priority", tr("Priority")); + m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base")); + m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base")); + m_ui.bgInfo->addCustomProperty("size", tr("Size")); + m_ui.bgInfo->addCustomProperty("offset", tr("Offset")); + m_ui.bgInfo->addCustomProperty("transform", tr("Xform")); break; #endif #ifdef M_CORE_GB

@@ -49,6 +56,9 @@ case PLATFORM_GB:

m_boundary = 1024; m_addressBase = GB_BASE_VRAM; m_addressWidth = 4; + m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base")); + m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base")); + m_ui.bgInfo->addCustomProperty("offset", tr("Offset")); break; #endif default:

@@ -143,39 +153,82 @@ void MapView::updateTilesGBA(bool force) {

{ CoreController::Interrupter interrupter(m_controller); int bitmap = -1; + int priority = -1; + int frame = 0; + QString offset(tr("N/A")); + QString transform(tr("N/A")); if (m_controller->platform() == PLATFORM_GBA) { - int mode = GBARegisterDISPCNTGetMode(static_cast<GBA*>(m_controller->thread()->core->board)->memory.io[REG_DISPCNT]); + uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io; + int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]); if (m_map == 2 && mode > 2) { bitmap = mode == 4 ? 1 : 0; + if (mode != 3) { + frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]); + } } + priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]); + if (mode == 0 || (mode == 1 && m_map != 2)) { + offset = QString("%1, %2") + .arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)]) + .arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]); + } else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) { + int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)]; + refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16; + int32_t refY = io[(REG_BG2Y_LO >> 1) + ((m_map - 2) << 2)]; + refY |= io[(REG_BG2Y_HI >> 1) + ((m_map - 2) << 2)] << 16; + refX <<= 4; + refY <<= 4; + refX >>= 4; + refY >>= 4; + offset = QString("%1\n%2").arg(refX / 65536., 0, 'f', 3).arg(refY / 65536., 0, 'f', 3); + transform = QString("%1 %2\n%3 %4") + .arg(io[(REG_BG2PA >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2) + .arg(io[(REG_BG2PB >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2) + .arg(io[(REG_BG2PC >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2) + .arg(io[(REG_BG2PD >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2); + + } + } + if (m_controller->platform() == PLATFORM_GB) { + uint8_t* io = static_cast<GB*>(m_controller->thread()->core->board)->memory.io; + int x = io[m_map == 0 ? 0x42 : 0x4A]; + int y = io[m_map == 0 ? 0x43 : 0x4B]; + offset = QString("%1, %2").arg(x).arg(y); } if (bitmap >= 0) { mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap); int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig); int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig); + m_ui.bgInfo->setCustomProperty("screenBase", QString("0x%1").arg(m_addressBase + bitmapCache->bitsStart[frame], 8, 16, QChar('0'))); + m_ui.bgInfo->setCustomProperty("charBase", tr("N/A")); + m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(width).arg(height)); + m_ui.bgInfo->setCustomProperty("priority", priority); + m_ui.bgInfo->setCustomProperty("offset", offset); + m_ui.bgInfo->setCustomProperty("transform", transform); m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32); uchar* bgBits = m_rawMap.bits(); for (int j = 0; j < height; ++j) { mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j); memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4); } + m_rawMap = m_rawMap.rgbSwapped(); } else { mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map); int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig); int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig); - m_rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32); - uchar* bgBits = m_rawMap.bits(); - for (int j = 0; j < tilesH; ++j) { - for (int i = 0; i < tilesW; ++i) { - mMapCacheCleanTile(mapCache, m_mapStatus, i, j); - } - for (int i = 0; i < 8; ++i) { - memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32); - } - } + m_ui.bgInfo->setCustomProperty("screenBase", QString("%0%1") + .arg(m_addressWidth == 8 ? "0x" : "") + .arg(m_addressBase + mapCache->mapStart, m_addressWidth, 16, QChar('0'))); + m_ui.bgInfo->setCustomProperty("charBase", QString("%0%1") + .arg(m_addressWidth == 8 ? "0x" : "") + .arg(m_addressBase + mapCache->tileCache->tileBase, m_addressWidth, 16, QChar('0'))); + m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(tilesW * 8).arg(tilesH * 8)); + m_ui.bgInfo->setCustomProperty("priority", priority); + m_ui.bgInfo->setCustomProperty("offset", offset); + m_ui.bgInfo->setCustomProperty("transform", transform); + m_rawMap = compositeMap(m_map, m_mapStatus); } } - m_rawMap = m_rawMap.rgbSwapped(); QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32)); if (m_ui.magnification->value() > 1) { map = map.scaled(map.size() * m_ui.magnification->value());
M src/platform/qt/MapView.uisrc/platform/qt/MapView.ui

@@ -6,18 +6,80 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>641</width> - <height>489</height> + <width>941</width> + <height>617</height> </rect> </property> <property name="windowTitle"> <string>Maps</string> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="bgLayout"/> + <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0"> + <item row="3" column="0"> + <widget class="QGBA::AssetTile" name="tile"/> + </item> + <item row="2" column="0"> + <widget class="QGBA::AssetInfo" name="bgInfo"> + <property name="title"> + <string/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"/> + </widget> + </item> + <item row="5" column="0"> + <widget class="QPushButton" name="exportButton"> + <property name="text"> + <string>Export</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="bgLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QSpinBox" name="magnification"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="suffix"> + <string>×</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>8</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Magnification</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> </item> - <item row="0" column="1" rowspan="5"> + <item row="1" column="1" rowspan="5" colspan="2"> <widget class="QScrollArea" name="scrollArea"> <property name="widgetResizable"> <bool>true</bool>

@@ -30,8 +92,8 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>457</width> - <height>463</height> + <width>613</width> + <height>601</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout">

@@ -71,62 +133,15 @@ </layout>

</widget> </widget> </item> - <item row="2" column="0"> - <widget class="QGBA::AssetTile" name="tile"/> - </item> - <item row="4" column="0"> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <item> - <widget class="QSpinBox" name="magnification"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="suffix"> - <string>×</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>8</number> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Magnification</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="0"> - <widget class="QPushButton" name="exportButton"> - <property name="text"> - <string>Export</string> - </property> - </widget> - </item> </layout> </widget> <customwidgets> + <customwidget> + <class>QGBA::AssetInfo</class> + <extends>QGroupBox</extends> + <header>AssetInfo.h</header> + <container>1</container> + </customwidget> <customwidget> <class>QGBA::AssetTile</class> <extends>QGroupBox</extends>
M src/platform/qt/ObjView.cppsrc/platform/qt/ObjView.cpp

@@ -19,9 +19,7 @@ #include <mgba/internal/gba/gba.h>

#endif #ifdef M_CORE_GB #include <mgba/internal/gb/gb.h> -#include <mgba/internal/gb/io.h> #endif -#include <mgba-util/png-io.h> #include <mgba-util/vfs.h> using namespace QGBA;

@@ -53,11 +51,7 @@ connect(m_ui.objId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ObjView::selectObj);

connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() { updateTiles(true); }); -#ifdef USE_PNG connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj); -#else - m_ui.exportButton->setVisible(false); -#endif } void ObjView::selectObj(int obj) {

@@ -77,79 +71,56 @@ m_ui.objId->setMaximum(127);

const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board); const GBAObj* obj = &gba->video.oam.obj[m_objId]; - unsigned shape = GBAObjAttributesAGetShape(obj->a); - unsigned size = GBAObjAttributesBGetSize(obj->b); - unsigned width = GBAVideoObjSizes[shape * 4 + size][0]; - unsigned height = GBAVideoObjSizes[shape * 4 + size][1]; - unsigned tile = GBAObjAttributesCGetTile(obj->c); - m_ui.tiles->setTileCount(width * height / 64); - m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value()); - m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value()); - unsigned palette = GBAObjAttributesCGetPalette(obj->c); - unsigned tileBase = tile; - unsigned paletteSet; - unsigned bits; + ObjInfo newInfo; + lookupObj(m_objId, &newInfo); + + m_ui.tiles->setTileCount(newInfo.width * newInfo.height); + m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value()); + m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value()); + unsigned tileBase = newInfo.tile; + unsigned tile = newInfo.tile; if (GBAObjAttributesAIs256Color(obj->a)) { m_ui.palette->setText("256-color"); - paletteSet = 3; m_ui.tile->setBoundary(1024, 1, 3); m_ui.tile->setPalette(0); m_boundary = 1024; - palette = 0; - tile /= 2; - bits = 8; + tileBase *= 2; } else { - m_ui.palette->setText(QString::number(palette)); - paletteSet = 2; + m_ui.palette->setText(QString::number(newInfo.paletteId)); m_ui.tile->setBoundary(2048, 0, 2); - m_ui.tile->setPalette(palette); - m_boundary = 2048; - bits = 4; + m_ui.tile->setPalette(newInfo.paletteId); } - ObjInfo newInfo{ - tile, - width / 8, - height / 8, - width / 8, - palette, - paletteSet, - bits - }; if (newInfo != m_objInfo) { force = true; } - GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues - if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) { - newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a)); - }; m_objInfo = newInfo; - m_tileOffset = tile; - mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, paletteSet); + m_tileOffset = newInfo.tile; + mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet); int i = 0; - for (int y = 0; y < height / 8; ++y) { - for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) { - const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, palette); + for (int y = 0; y < newInfo.height; ++y) { + for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) { + const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, palette)); + m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, newInfo.paletteId)); } } - tile += newInfo.stride - width / 8; - tileBase += newInfo.stride - width / 8; + tile += newInfo.stride - newInfo.width; + tileBase += newInfo.stride - newInfo.width; } - m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b))); - m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a))); - m_ui.w->setText(QString::number(width)); - m_ui.h->setText(QString::number(height)); + m_ui.x->setText(QString::number(newInfo.x)); + m_ui.y->setText(QString::number(newInfo.y)); + m_ui.w->setText(QString::number(newInfo.width * 8)); + m_ui.h->setText(QString::number(newInfo.height * 8)); m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0'))); - m_ui.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c))); - m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b)); - m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b)); - m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a)); + m_ui.priority->setText(QString::number(newInfo.priority)); + m_ui.flippedH->setChecked(newInfo.hflip); + m_ui.flippedV->setChecked(newInfo.vflip); + m_ui.enabled->setChecked(newInfo.enabled); m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a)); m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));

@@ -182,39 +153,17 @@ m_ui.objId->setMaximum(39);

const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board); const GBObj* obj = &gb->video.oam.obj[m_objId]; + ObjInfo newInfo; + lookupObj(m_objId, &newInfo); + mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0); - unsigned width = 8; - unsigned height = 8; - GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC]; - if (GBRegisterLCDCIsObjSize(lcdc)) { - height = 16; - } - unsigned tile = obj->tile; - m_ui.tiles->setTileCount(width * height / 64); + unsigned tile = newInfo.tile; + m_ui.tiles->setTileCount(newInfo.height); m_ui.tile->setBoundary(1024, 0, 0); - m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value()); - m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value()); - unsigned palette = 0; - if (gb->model >= GB_MODEL_CGB) { - if (GBObjAttributesIsBank(obj->attr)) { - tile += 512; - } - palette = GBObjAttributesGetCGBPalette(obj->attr); - } else { - palette = GBObjAttributesGetPalette(obj->attr); - } - m_ui.palette->setText(QString::number(palette)); - palette += 8; + m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value()); + m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value()); + m_ui.palette->setText(QString::number(newInfo.paletteId - 8)); - ObjInfo newInfo{ - tile, - 1, - height / 8, - 1, - palette, - 0, - 2 - }; if (newInfo != m_objInfo) { force = true; }

@@ -223,27 +172,27 @@ m_tileOffset = tile;

m_boundary = 1024; int i = 0; - m_ui.tile->setPalette(palette); - for (int y = 0; y < height / 8; ++y, ++i) { + m_ui.tile->setPalette(newInfo.paletteId); + for (int y = 0; y < newInfo.height; ++y, ++i) { unsigned t = tile + i; - const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, palette); + const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, palette)); + m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, newInfo.paletteId)); } } - m_ui.x->setText(QString::number(obj->x)); - m_ui.y->setText(QString::number(obj->y)); - m_ui.w->setText(QString::number(width)); - m_ui.h->setText(QString::number(height)); + m_ui.x->setText(QString::number(newInfo.x)); + m_ui.y->setText(QString::number(newInfo.y)); + m_ui.w->setText(QString::number(8)); + m_ui.h->setText(QString::number(newInfo.height * 8)); m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0'))); - m_ui.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr))); - m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr)); - m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr)); - m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160); + m_ui.priority->setText(QString::number(newInfo.priority)); + m_ui.flippedH->setChecked(newInfo.hflip); + m_ui.flippedV->setChecked(newInfo.vflip); + m_ui.enabled->setChecked(newInfo.enabled); m_ui.doubleSize->setChecked(false); m_ui.mosaic->setChecked(false); m_ui.transform->setText(tr("N/A"));

@@ -251,51 +200,10 @@ m_ui.mode->setText(tr("N/A"));

} #endif -#ifdef USE_PNG void ObjView::exportObj() { QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"), tr("Portable Network Graphics (*.png)")); - VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); - if (!vf) { - LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename); - return; - } - CoreController::Interrupter interrupter(m_controller); - png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8); - - mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, m_objInfo.paletteSet); - const color_t* rawPalette = mTileCacheGetPalette(tileCache, m_objInfo.paletteId); - unsigned colors = 1 << m_objInfo.bits; - uint32_t palette[256]; - - palette[0] = rawPalette[0]; - for (unsigned c = 1; c < colors && c < 256; ++c) { - palette[c] = rawPalette[c] | 0xFF000000; - } - PNGWritePalette(png, info, palette, colors); - - uint8_t* buffer = new uint8_t[m_objInfo.width * m_objInfo.height * 8 * 8]; - unsigned t = m_objInfo.tile; - for (int y = 0; y < m_objInfo.height; ++y) { - for (int x = 0; x < m_objInfo.width; ++x, ++t) { - compositeTile(static_cast<const void*>(mTileCacheGetVRAM(tileCache, t)), reinterpret_cast<color_t*>(buffer), m_objInfo.width * 8, x * 8, y * 8, m_objInfo.bits); - } - t += m_objInfo.stride - m_objInfo.width; - } - PNGWritePixels8(png, m_objInfo.width * 8, m_objInfo.height * 8, m_objInfo.width * 8, static_cast<void*>(buffer)); - PNGWriteClose(png, info); - delete[] buffer; - vf->close(vf); -} -#endif - -bool ObjView::ObjInfo::operator!=(const ObjInfo& other) { - return other.tile != tile || - other.width != width || - other.height != height || - other.stride != stride || - other.paletteId != paletteId || - other.paletteSet != paletteSet; + QImage obj = compositeObj(m_objInfo); + obj.save(filename, "PNG"); }
M src/platform/qt/ObjView.hsrc/platform/qt/ObjView.h

@@ -21,10 +21,8 @@

public: ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); -#ifdef USE_PNG public slots: void exportObj(); -#endif private slots: void selectObj(int);

@@ -43,17 +41,7 @@

std::shared_ptr<CoreController> m_controller; mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size int m_objId = 0; - struct ObjInfo { - unsigned tile; - unsigned width; - unsigned height; - unsigned stride; - unsigned paletteId; - unsigned paletteSet; - unsigned bits; - - bool operator!=(const ObjInfo&); - } m_objInfo = {}; + ObjInfo m_objInfo = {}; int m_tileOffset; int m_boundary;
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -363,7 +363,6 @@ saveSetting("ds.firmware", m_ui.dsFirmware);

saveSetting("useCgbColors", m_ui.useCgbColors); saveSetting("useBios", m_ui.useBios); saveSetting("skipBios", m_ui.skipBios); - saveSetting("audioBuffers", m_ui.audioBufferSize); saveSetting("sampleRate", m_ui.sampleRate); saveSetting("videoSync", m_ui.videoSync); saveSetting("audioSync", m_ui.audioSync);

@@ -400,6 +399,11 @@ saveSetting("logToStdout", m_ui.logToStdout);

saveSetting("logFile", m_ui.logFile); saveSetting("useDiscordPresence", m_ui.useDiscordPresence); saveSetting("gba.audioHle", m_ui.audioHle); + + if (m_ui.audioBufferSize->currentText().toInt() > 8192) { + m_ui.audioBufferSize->setCurrentText("8192"); + } + saveSetting("audioBuffers", m_ui.audioBufferSize); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1");
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -30,6 +30,7 @@ #include "DebuggerConsole.h"

#include "DebuggerConsoleController.h" #include "Display.h" #include "CoreController.h" +#include "FrameView.h" #include "GBAApp.h" #include "GDBController.h" #include "GDBWindow.h"

@@ -971,6 +972,7 @@ connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing);

connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted); connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); + connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext); attachWidget(m_display.get()); m_display->startDrawing(m_controller);

@@ -1527,7 +1529,7 @@ m_overrideView->show();

m_overrideView->recheck(); }, "tools"); - m_actions.addAction(tr("Game &Pak sensors..."), "sensorWindow", [this]() { + m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", [this]() { if (!m_sensorView) { m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController)); if (m_controller) {

@@ -1557,6 +1559,26 @@ addGameAction(tr("View &palette..."), "paletteWindow", openControllerTView<PaletteView>(), "tools");

addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools"); addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "tools"); addGameAction(tr("View &map..."), "mapWindow", openControllerTView<MapView>(), "tools"); + +#ifdef M_CORE_GBA + Action* frameWindow = addGameAction(tr("&Frame inspector..."), "frameWindow", [this]() { + if (!m_frameView) { + m_frameView = new FrameView(m_controller); + connect(this, &Window::shutdown, this, [this]() { + if (m_frameView) { + m_frameView->close(); + } + }); + connect(m_frameView, &QObject::destroyed, this, [this]() { + m_frameView = nullptr; + }); + m_frameView->setAttribute(Qt::WA_DeleteOnClose); + } + m_frameView->show(); + }, "tools"); + m_platformActions.insert(PLATFORM_GBA, frameWindow); +#endif + addGameAction(tr("View memory..."), "memoryView", openControllerTView<MemoryView>(), "tools"); addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools");

@@ -1744,11 +1766,8 @@ }

} void Window::updateFrame() { - QSize size = m_controller->screenDimensions(); - QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), size.width(), size.height(), - size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888); QPixmap pixmap; - pixmap.convertFromImage(currentImage); + pixmap.convertFromImage(m_controller->getPixels()); m_screenWidget->setPixmap(pixmap); emit paused(true); }

@@ -1834,6 +1853,7 @@ connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing);

connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted); connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); + connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext); connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver); connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame);
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -32,6 +32,7 @@ class CoreController;

class CoreManager; class DebuggerConsoleController; class Display; +class FrameView; class GDBController; class GIFView; class LibraryController;

@@ -213,6 +214,7 @@ bool m_hitUnimplementedBiosCall;

std::unique_ptr<OverrideView> m_overrideView; std::unique_ptr<SensorView> m_sensorView; + FrameView* m_frameView = nullptr; #ifdef USE_FFMPEG VideoView* m_videoView = nullptr;
M src/platform/qt/ts/medusa-emu-de.tssrc/platform/qt/ts/medusa-emu-de.ts

@@ -255,6 +255,49 @@ <translation>Unterbrechen</translation>

</message> </context> <context> + <name>FrameView</name> + <message> + <location filename="../FrameView.ui" line="14"/> + <source>Inspect frame</source> + <translation>Bild beobachten</translation> + </message> + <message> + <location filename="../FrameView.ui" line="28"/> + <source>×</source> + <translation>×</translation> + </message> + <message> + <location filename="../FrameView.ui" line="41"/> + <source>Magnification</source> + <translation>Vergrößerung</translation> + </message> + <message> + <location filename="../FrameView.ui" line="50"/> + <source>Freeze frame</source> + <translation>Bild einfrieren</translation> + </message> + <message> + <location filename="../FrameView.ui" line="84"/> + <source>Backdrop color</source> + <translation>Hintergrundfarbe</translation> + </message> + <message> + <location filename="../FrameView.ui" line="132"/> + <source>Disable scanline effects</source> + <translation>Scanline-Effekte deaktivieren</translation> + </message> + <message> + <location filename="../FrameView.ui" line="145"/> + <source>Export</source> + <translation>Exportieren</translation> + </message> + <message> + <location filename="../FrameView.ui" line="152"/> + <source>Reset</source> + <translation>Zurücksetzen</translation> + </message> +</context> +<context> <name>GIFView</name> <message> <location filename="../GIFView.ui" line="14"/>

@@ -546,17 +589,17 @@ <source>Maps</source>

<translation>Maps</translation> </message> <message> - <location filename="../MapView.ui" line="101"/> + <location filename="../MapView.ui" line="61"/> <source>×</source> <translation>×</translation> </message> <message> - <location filename="../MapView.ui" line="114"/> + <location filename="../MapView.ui" line="74"/> <source>Magnification</source> <translation>Vergrößerung</translation> </message> <message> - <location filename="../MapView.ui" line="123"/> + <location filename="../MapView.ui" line="31"/> <source>Export</source> <translation>Exportieren</translation> </message>

@@ -1231,14 +1274,14 @@ </context>

<context> <name>QGBA::AssetTile</name> <message> - <location filename="../AssetTile.cpp" line="112"/> + <location filename="../AssetTile.cpp" line="104"/> <source>%0%1%2</source> <translation>%0%1%2</translation> </message> <message> - <location filename="../AssetTile.cpp" line="148"/> - <location filename="../AssetTile.cpp" line="149"/> - <location filename="../AssetTile.cpp" line="150"/> + <location filename="../AssetTile.cpp" line="140"/> + <location filename="../AssetTile.cpp" line="141"/> + <location filename="../AssetTile.cpp" line="142"/> <source>0x%0 (%1)</source> <translation>0x%0 (%1)</translation> </message>

@@ -1289,22 +1332,22 @@ </context>

<context> <name>QGBA::CoreController</name> <message> - <location filename="../CoreController.cpp" line="568"/> + <location filename="../CoreController.cpp" line="594"/> <source>Failed to open save file: %1</source> <translation>Fehler beim Öffnen der Speicherdatei: %1</translation> </message> <message> - <location filename="../CoreController.cpp" line="597"/> + <location filename="../CoreController.cpp" line="623"/> <source>Failed to open game file: %1</source> <translation>Fehler beim Öffnen der Spieldatei: %1</translation> </message> <message> - <location filename="../CoreController.cpp" line="662"/> + <location filename="../CoreController.cpp" line="688"/> <source>Failed to open snapshot file for reading: %1</source> <translation>Konnte Snapshot-Datei %1 nicht zum Lesen öffnen</translation> </message> <message> - <location filename="../CoreController.cpp" line="678"/> + <location filename="../CoreController.cpp" line="704"/> <source>Failed to open snapshot file for writing: %1</source> <translation>Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen</translation> </message>

@@ -1315,6 +1358,49 @@ <message>

<location filename="../CoreManager.cpp" line="54"/> <source>Failed to open game file: %1</source> <translation>Fehler beim Öffnen der Spieldatei: %1</translation> + </message> +</context> +<context> + <name>QGBA::FrameView</name> + <message> + <location filename="../FrameView.cpp" line="430"/> + <source>Export frame</source> + <translation>Bild exportieren</translation> + </message> + <message> + <location filename="../FrameView.cpp" line="431"/> + <source>Portable Network Graphics (*.png)</source> + <translation>Portable Network Graphics (*.png)</translation> + </message> + <message> + <location filename="../FrameView.cpp" line="449"/> + <source>None</source> + <translation>Keine</translation> + </message> + <message> + <location filename="../FrameView.cpp" line="451"/> + <source>Background</source> + <translation>Hintergrund</translation> + </message> + <message> + <location filename="../FrameView.cpp" line="454"/> + <source>Window</source> + <translation>Fenster</translation> + </message> + <message> + <location filename="../FrameView.cpp" line="457"/> + <source>Sprite</source> + <translation>Sprite</translation> + </message> + <message> + <location filename="../FrameView.cpp" line="460"/> + <source>Backdrop</source> + <translation>Hintergrund</translation> + </message> + <message> + <location filename="../FrameView.cpp" line="466"/> + <source>%1 %2</source> + <translation>%1 %2</translation> </message> </context> <context>

@@ -2940,47 +3026,87 @@ </context>

<context> <name>QGBA::MapView</name> <message> - <location filename="../MapView.cpp" line="87"/> + <location filename="../MapView.cpp" line="46"/> + <source>Priority</source> + <translation>Priorität</translation> + </message> + <message> + <location filename="../MapView.cpp" line="47"/> + <location filename="../MapView.cpp" line="59"/> + <source>Map base</source> + <translation>Map-Basis</translation> + </message> + <message> + <location filename="../MapView.cpp" line="48"/> + <location filename="../MapView.cpp" line="60"/> + <source>Tile base</source> + <translation>Tile-Basis</translation> + </message> + <message> + <location filename="../MapView.cpp" line="49"/> + <source>Size</source> + <translation>Größe</translation> + </message> + <message> + <location filename="../MapView.cpp" line="50"/> + <location filename="../MapView.cpp" line="61"/> + <source>Offset</source> + <translation>Versatz</translation> + </message> + <message> + <location filename="../MapView.cpp" line="51"/> + <source>Xform</source> + <translation>Xform</translation> + </message> + <message> + <location filename="../MapView.cpp" line="97"/> <source>Map Addr.</source> <translation>Map-Addr.</translation> </message> <message> - <location filename="../MapView.cpp" line="88"/> + <location filename="../MapView.cpp" line="98"/> <source>Mirror</source> <translation>Spiegel</translation> </message> <message> - <location filename="../MapView.cpp" line="116"/> + <location filename="../MapView.cpp" line="126"/> <source>None</source> <translation>Keiner</translation> </message> <message> - <location filename="../MapView.cpp" line="118"/> + <location filename="../MapView.cpp" line="128"/> <source>Both</source> <translation>Beidseitig</translation> </message> <message> - <location filename="../MapView.cpp" line="120"/> + <location filename="../MapView.cpp" line="130"/> <source>Horizontal</source> <translation>Horizontal</translation> </message> <message> - <location filename="../MapView.cpp" line="122"/> + <location filename="../MapView.cpp" line="132"/> <source>Vertical</source> <translation>Vertikal</translation> </message> <message> - <location filename="../MapView.cpp" line="194"/> + <location filename="../MapView.cpp" line="158"/> + <location filename="../MapView.cpp" line="159"/> + <location filename="../MapView.cpp" line="203"/> + <source>N/A</source> + <translation>N/A</translation> + </message> + <message> + <location filename="../MapView.cpp" line="247"/> <source>Export map</source> <translation>Map exportieren</translation> </message> <message> - <location filename="../MapView.cpp" line="195"/> + <location filename="../MapView.cpp" line="248"/> <source>Portable Network Graphics (*.png)</source> <translation>Portable Network Graphics (*.png)</translation> </message> <message> - <location filename="../MapView.cpp" line="198"/> + <location filename="../MapView.cpp" line="251"/> <source>Failed to open output PNG file: %1</source> <translation>Fehler beim Öffnen der Ausgabe-PNG-Datei: %1</translation> </message>

@@ -3074,56 +3200,51 @@ </context>

<context> <name>QGBA::ObjView</name> <message> - <location filename="../ObjView.cpp" line="148"/> - <location filename="../ObjView.cpp" line="242"/> + <location filename="../ObjView.cpp" line="119"/> + <location filename="../ObjView.cpp" line="191"/> <source>0x%0</source> <translation>0x%0</translation> </message> <message> - <location filename="../ObjView.cpp" line="159"/> + <location filename="../ObjView.cpp" line="130"/> <source>Off</source> <translation>Aus</translation> </message> <message> - <location filename="../ObjView.cpp" line="164"/> + <location filename="../ObjView.cpp" line="135"/> <source>Normal</source> <translation>Normal</translation> </message> <message> - <location filename="../ObjView.cpp" line="167"/> + <location filename="../ObjView.cpp" line="138"/> <source>Trans</source> <translation>Trans</translation> </message> <message> - <location filename="../ObjView.cpp" line="170"/> + <location filename="../ObjView.cpp" line="141"/> <source>OBJWIN</source> <translation>OBJWIN</translation> </message> <message> - <location filename="../ObjView.cpp" line="173"/> + <location filename="../ObjView.cpp" line="144"/> <source>Invalid</source> <translation>Ungültig</translation> </message> <message> - <location filename="../ObjView.cpp" line="249"/> - <location filename="../ObjView.cpp" line="250"/> + <location filename="../ObjView.cpp" line="198"/> + <location filename="../ObjView.cpp" line="199"/> <source>N/A</source> <translation>N/A</translation> </message> <message> - <location filename="../ObjView.cpp" line="256"/> + <location filename="../ObjView.cpp" line="204"/> <source>Export sprite</source> <translation>Sprite exportieren</translation> </message> <message> - <location filename="../ObjView.cpp" line="257"/> + <location filename="../ObjView.cpp" line="205"/> <source>Portable Network Graphics (*.png)</source> <translation>Portable Network Graphics (*.png)</translation> - </message> - <message> - <location filename="../ObjView.cpp" line="260"/> - <source>Failed to open output PNG file: %1</source> - <translation>Fehler beim Öffnen der Ausgabe-PNG-Datei: %1</translation> </message> </context> <context>

@@ -3321,7 +3442,7 @@ </context>

<context> <name>QGBA::Window</name> <message> - <location filename="../Window.cpp" line="292"/> + <location filename="../Window.cpp" line="293"/> <source>Game Boy Advance ROMs (%1)</source> <translation>Game Boy Advance-ROMs (%1)</translation> </message>

@@ -3331,78 +3452,78 @@ <source>DS ROMs (%1)</source>

<translation>DS-ROMs (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="309"/> + <location filename="../Window.cpp" line="310"/> <source>Game Boy ROMs (%1)</source> <translation>Game Boy-ROMs (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="313"/> + <location filename="../Window.cpp" line="314"/> <source>All ROMs (%1)</source> <translation>Alle ROMs (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="314"/> + <location filename="../Window.cpp" line="315"/> <source>%1 Video Logs (*.mvl)</source> <translation>%1 Video-Logs (*.mvl)</translation> </message> <message> - <location filename="../Window.cpp" line="329"/> + <location filename="../Window.cpp" line="330"/> <source>Archives (%1)</source> <translation>Archive (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="334"/> - <location filename="../Window.cpp" line="342"/> - <location filename="../Window.cpp" line="369"/> + <location filename="../Window.cpp" line="335"/> + <location filename="../Window.cpp" line="343"/> + <location filename="../Window.cpp" line="370"/> <source>Select ROM</source> <translation>ROM auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="377"/> + <location filename="../Window.cpp" line="378"/> <source>Game Boy Advance save files (%1)</source> <translation>Game Boy Advance-Speicherdateien (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="378"/> - <location filename="../Window.cpp" line="439"/> - <location filename="../Window.cpp" line="446"/> + <location filename="../Window.cpp" line="379"/> + <location filename="../Window.cpp" line="440"/> + <location filename="../Window.cpp" line="447"/> <source>Select save</source> <translation>Speicherdatei wählen</translation> </message> <message> - <location filename="../Window.cpp" line="386"/> + <location filename="../Window.cpp" line="387"/> <source>mGBA savestate files (%1)</source> <translation>mGBA Savestate-Dateien (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="388"/> - <location filename="../Window.cpp" line="393"/> + <location filename="../Window.cpp" line="389"/> + <location filename="../Window.cpp" line="394"/> <source>Select savestate</source> <translation>Savestate auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="415"/> + <location filename="../Window.cpp" line="416"/> <source>Select patch</source> <translation>Patch wählen</translation> </message> <message> - <location filename="../Window.cpp" line="415"/> + <location filename="../Window.cpp" line="416"/> <source>Patches (*.ips *.ups *.bps)</source> <translation>Patches (*.ips *.ups *.bps)</translation> </message> <message> - <location filename="../Window.cpp" line="432"/> + <location filename="../Window.cpp" line="433"/> <source>Select image</source> <translation>Bild auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="432"/> + <location filename="../Window.cpp" line="433"/> <source>Image file (*.png *.gif *.jpg *.jpeg);;All files (*)</source> <translation>Bild-Datei (*.png *.gif *.jpg *.jpeg);;Alle Dateien (*)</translation> </message> <message> - <location filename="../Window.cpp" line="439"/> - <location filename="../Window.cpp" line="446"/> + <location filename="../Window.cpp" line="440"/> + <location filename="../Window.cpp" line="447"/> <source>GameShark saves (*.sps *.xps)</source> <translation>GameShark-Speicherdaten (*.sps *.xps)</translation> </message>

@@ -3417,22 +3538,22 @@ <source>DS support requires dumps of the BIOS and firmware.</source>

<translation>DS-Unterstützung erfordert ein Abbild des BIOS und der Firmware.</translation> </message> <message> - <location filename="../Window.cpp" line="473"/> + <location filename="../Window.cpp" line="474"/> <source>Select video log</source> <translation>Video-Log auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="473"/> + <location filename="../Window.cpp" line="474"/> <source>Video logs (*.mvl)</source> <translation>Video-Logs (*.mvl)</translation> </message> <message> - <location filename="../Window.cpp" line="831"/> + <location filename="../Window.cpp" line="833"/> <source>Crash</source> <translation>Absturz</translation> </message> <message> - <location filename="../Window.cpp" line="832"/> + <location filename="../Window.cpp" line="834"/> <source>The game has crashed with the following error: %1</source>

@@ -3441,428 +3562,428 @@

%1</translation> </message> <message> - <location filename="../Window.cpp" line="839"/> + <location filename="../Window.cpp" line="841"/> <source>Couldn&apos;t Load</source> <translation>Konnte nicht geladen werden</translation> </message> <message> - <location filename="../Window.cpp" line="840"/> + <location filename="../Window.cpp" line="842"/> <source>Could not load game. Are you sure it&apos;s in the correct format?</source> <translation>Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt?</translation> </message> <message> - <location filename="../Window.cpp" line="853"/> + <location filename="../Window.cpp" line="855"/> <source>Unimplemented BIOS call</source> <translation>Nicht implementierter BIOS-Aufruf</translation> </message> <message> - <location filename="../Window.cpp" line="854"/> + <location filename="../Window.cpp" line="856"/> <source>This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience.</source> <translation>Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS.</translation> </message> <message> - <location filename="../Window.cpp" line="937"/> + <location filename="../Window.cpp" line="940"/> <source>Really make portable?</source> <translation>Portablen Modus wirklich aktivieren?</translation> </message> <message> - <location filename="../Window.cpp" line="938"/> + <location filename="../Window.cpp" line="941"/> <source>This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?</source> <translation>Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren?</translation> </message> <message> - <location filename="../Window.cpp" line="946"/> + <location filename="../Window.cpp" line="949"/> <source>Restart needed</source> <translation>Neustart benötigt</translation> </message> <message> - <location filename="../Window.cpp" line="947"/> + <location filename="../Window.cpp" line="950"/> <source>Some changes will not take effect until the emulator is restarted.</source> <translation>Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde.</translation> </message> <message> - <location filename="../Window.cpp" line="995"/> + <location filename="../Window.cpp" line="998"/> <source> - Player %1 of %2</source> <translation> - Spieler %1 von %2</translation> </message> <message> - <location filename="../Window.cpp" line="1006"/> + <location filename="../Window.cpp" line="1009"/> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> - <location filename="../Window.cpp" line="1008"/> + <location filename="../Window.cpp" line="1011"/> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> - <location filename="../Window.cpp" line="1010"/> + <location filename="../Window.cpp" line="1013"/> <source>%1 - %2 (%3 fps) - %4</source> <translation>%1 - %2 (%3 Bilder/Sekunde) - %4</translation> </message> <message> - <location filename="../Window.cpp" line="1048"/> + <location filename="../Window.cpp" line="1051"/> <source>&amp;File</source> <translation>&amp;Datei</translation> </message> <message> - <location filename="../Window.cpp" line="1050"/> + <location filename="../Window.cpp" line="1053"/> <source>Load &amp;ROM...</source> <translation>&amp;ROM laden...</translation> </message> <message> - <location filename="../Window.cpp" line="1053"/> + <location filename="../Window.cpp" line="1056"/> <source>Load ROM in archive...</source> <translation>ROM aus Archiv laden...</translation> </message> <message> - <location filename="../Window.cpp" line="1057"/> + <location filename="../Window.cpp" line="1060"/> <source>Load alternate save...</source> <translation>Alternative Speicherdatei laden...</translation> </message> <message> - <location filename="../Window.cpp" line="1060"/> + <location filename="../Window.cpp" line="1063"/> <source>Load temporary save...</source> <translation>Temporäre Speicherdatei laden...</translation> </message> <message> - <location filename="../Window.cpp" line="1064"/> + <location filename="../Window.cpp" line="1067"/> <source>Load &amp;patch...</source> <translation>&amp;Patch laden...</translation> </message> <message> - <location filename="../Window.cpp" line="1067"/> + <location filename="../Window.cpp" line="1070"/> <source>Boot BIOS</source> <translation>BIOS booten</translation> </message> <message> - <location filename="../Window.cpp" line="1072"/> + <location filename="../Window.cpp" line="1075"/> <source>Replace ROM...</source> <translation>ROM ersetzen...</translation> </message> <message> - <location filename="../Window.cpp" line="1074"/> + <location filename="../Window.cpp" line="1077"/> <source>ROM &amp;info...</source> <translation>ROM-&amp;Informationen...</translation> </message> <message> - <location filename="../Window.cpp" line="1076"/> + <location filename="../Window.cpp" line="1079"/> <source>Recent</source> <translation>Zuletzt verwendet</translation> </message> <message> - <location filename="../Window.cpp" line="1079"/> + <location filename="../Window.cpp" line="1082"/> <source>Make portable</source> <translation>Portablen Modus aktivieren</translation> </message> <message> - <location filename="../Window.cpp" line="1082"/> + <location filename="../Window.cpp" line="1085"/> <source>&amp;Load state</source> <translation>Savestate (aktueller Zustand) &amp;laden</translation> </message> <message> - <location filename="../Window.cpp" line="1087"/> + <location filename="../Window.cpp" line="1090"/> <source>Load state file...</source> <translation>Ssavestate-Datei laden...</translation> </message> <message> - <location filename="../Window.cpp" line="1092"/> + <location filename="../Window.cpp" line="1095"/> <source>&amp;Save state</source> <translation>Savestate (aktueller Zustand) &amp;speichern</translation> </message> <message> - <location filename="../Window.cpp" line="1097"/> + <location filename="../Window.cpp" line="1100"/> <source>Save state file...</source> <translation>Savestate-Datei speichern...</translation> </message> <message> - <location filename="../Window.cpp" line="1102"/> + <location filename="../Window.cpp" line="1105"/> <source>Quick load</source> <translation>Schnell laden</translation> </message> <message> - <location filename="../Window.cpp" line="1103"/> + <location filename="../Window.cpp" line="1106"/> <source>Quick save</source> <translation>Schnell speichern</translation> </message> <message> - <location filename="../Window.cpp" line="1105"/> + <location filename="../Window.cpp" line="1108"/> <source>Load recent</source> <translation>Lade zuletzt gespeicherten Savestate</translation> </message> <message> - <location filename="../Window.cpp" line="1110"/> + <location filename="../Window.cpp" line="1113"/> <source>Save recent</source> <translation>Speichere aktuellen Zustand</translation> </message> <message> - <location filename="../Window.cpp" line="1118"/> + <location filename="../Window.cpp" line="1121"/> <source>Undo load state</source> <translation>Laden des Savestate rückgängig machen</translation> </message> <message> - <location filename="../Window.cpp" line="1123"/> + <location filename="../Window.cpp" line="1126"/> <source>Undo save state</source> <translation>Speichern des Savestate rückgängig machen</translation> </message> <message> - <location filename="../Window.cpp" line="1132"/> - <location filename="../Window.cpp" line="1137"/> + <location filename="../Window.cpp" line="1135"/> + <location filename="../Window.cpp" line="1140"/> <source>State &amp;%1</source> <translation>Savestate &amp;%1</translation> </message> <message> - <location filename="../Window.cpp" line="1144"/> + <location filename="../Window.cpp" line="1147"/> <source>Load camera image...</source> <translation>Lade Kamerabild...</translation> </message> <message> - <location filename="../Window.cpp" line="1148"/> + <location filename="../Window.cpp" line="1151"/> <source>Import GameShark Save</source> <translation>Importiere GameShark-Speicherstand</translation> </message> <message> - <location filename="../Window.cpp" line="1151"/> + <location filename="../Window.cpp" line="1154"/> <source>Export GameShark Save</source> <translation>Exportiere GameShark-Speicherstand</translation> </message> <message> - <location filename="../Window.cpp" line="1156"/> + <location filename="../Window.cpp" line="1159"/> <source>New multiplayer window</source> <translation>Neues Multiplayer-Fenster</translation> </message> <message> - <location filename="../Window.cpp" line="1167"/> + <location filename="../Window.cpp" line="1170"/> <source>E&amp;xit</source> <translation>&amp;Beenden</translation> </message> <message> - <location filename="../Window.cpp" line="1170"/> + <location filename="../Window.cpp" line="1173"/> <source>&amp;Emulation</source> <translation>&amp;Emulation</translation> </message> <message> - <location filename="../Window.cpp" line="1171"/> + <location filename="../Window.cpp" line="1174"/> <source>&amp;Reset</source> <translation>Zu&amp;rücksetzen</translation> </message> <message> - <location filename="../Window.cpp" line="1175"/> + <location filename="../Window.cpp" line="1178"/> <source>Sh&amp;utdown</source> <translation>Schli&amp;eßen</translation> </message> <message> - <location filename="../Window.cpp" line="1180"/> + <location filename="../Window.cpp" line="1183"/> <source>Yank game pak</source> <translation>Spielmodul herausziehen</translation> </message> <message> - <location filename="../Window.cpp" line="1187"/> + <location filename="../Window.cpp" line="1190"/> <source>&amp;Pause</source> <translation>&amp;Pause</translation> </message> <message> - <location filename="../Window.cpp" line="1196"/> + <location filename="../Window.cpp" line="1199"/> <source>&amp;Next frame</source> <translation>&amp;Nächstes Bild</translation> </message> <message> - <location filename="../Window.cpp" line="1202"/> + <location filename="../Window.cpp" line="1205"/> <source>Fast forward (held)</source> <translation>Schneller Vorlauf (gehalten)</translation> </message> <message> - <location filename="../Window.cpp" line="1208"/> + <location filename="../Window.cpp" line="1211"/> <source>&amp;Fast forward</source> <translation>Schneller &amp;Vorlauf</translation> </message> <message> - <location filename="../Window.cpp" line="1212"/> + <location filename="../Window.cpp" line="1215"/> <source>Fast forward speed</source> <translation>Vorlauf-Geschwindigkeit</translation> </message> <message> - <location filename="../Window.cpp" line="1217"/> + <location filename="../Window.cpp" line="1220"/> <source>Unbounded</source> <translation>Unbegrenzt</translation> </message> <message> - <location filename="../Window.cpp" line="1221"/> + <location filename="../Window.cpp" line="1224"/> <source>%0x</source> <translation>%0x</translation> </message> <message> - <location filename="../Window.cpp" line="1225"/> + <location filename="../Window.cpp" line="1228"/> <source>Rewind (held)</source> <translation>Zurückspulen (gehalten)</translation> </message> <message> - <location filename="../Window.cpp" line="1232"/> + <location filename="../Window.cpp" line="1235"/> <source>Re&amp;wind</source> <translation>Zur&amp;ückspulen</translation> </message> <message> - <location filename="../Window.cpp" line="1237"/> + <location filename="../Window.cpp" line="1240"/> <source>Step backwards</source> <translation>Schrittweiser Rücklauf</translation> </message> <message> - <location filename="../Window.cpp" line="1243"/> + <location filename="../Window.cpp" line="1246"/> <source>Sync to &amp;video</source> <translation>Mit &amp;Video synchronisieren</translation> </message> <message> - <location filename="../Window.cpp" line="1250"/> + <location filename="../Window.cpp" line="1253"/> <source>Sync to &amp;audio</source> <translation>Mit &amp;Audio synchronisieren</translation> </message> <message> - <location filename="../Window.cpp" line="1258"/> + <location filename="../Window.cpp" line="1261"/> <source>Solar sensor</source> - <translation>Solar-Sensor</translation> + <translation>Sonnen-Sensor</translation> </message> <message> - <location filename="../Window.cpp" line="1259"/> + <location filename="../Window.cpp" line="1262"/> <source>Increase solar level</source> <translation>Sonnen-Level erhöhen</translation> </message> <message> - <location filename="../Window.cpp" line="1260"/> + <location filename="../Window.cpp" line="1263"/> <source>Decrease solar level</source> <translation>Sonnen-Level verringern</translation> </message> <message> - <location filename="../Window.cpp" line="1261"/> + <location filename="../Window.cpp" line="1264"/> <source>Brightest solar level</source> <translation>Hellster Sonnen-Level</translation> </message> <message> - <location filename="../Window.cpp" line="1264"/> + <location filename="../Window.cpp" line="1267"/> <source>Darkest solar level</source> <translation>Dunkelster Sonnen-Level</translation> </message> <message> - <location filename="../Window.cpp" line="1270"/> + <location filename="../Window.cpp" line="1273"/> <source>Brightness %1</source> <translation>Helligkeit %1</translation> </message> <message> - <location filename="../Window.cpp" line="1285"/> + <location filename="../Window.cpp" line="1288"/> <source>BattleChip Gate...</source> <translation>BattleChip Gate...</translation> </message> <message> - <location filename="../Window.cpp" line="1289"/> + <location filename="../Window.cpp" line="1292"/> <source>Audio/&amp;Video</source> <translation>Audio/&amp;Video</translation> </message> <message> - <location filename="../Window.cpp" line="1290"/> + <location filename="../Window.cpp" line="1293"/> <source>Frame size</source> <translation>Bildgröße</translation> </message> <message> - <location filename="../Window.cpp" line="1317"/> + <location filename="../Window.cpp" line="1320"/> <source>Toggle fullscreen</source> <translation>Vollbildmodus umschalten</translation> </message> <message> - <location filename="../Window.cpp" line="1320"/> + <location filename="../Window.cpp" line="1323"/> <source>Lock aspect ratio</source> <translation>Seitenverhältnis korrigieren</translation> </message> <message> - <location filename="../Window.cpp" line="1332"/> + <location filename="../Window.cpp" line="1335"/> <source>Force integer scaling</source> <translation>Pixelgenaue Skalierung (Integer scaling)</translation> </message> <message> - <location filename="../Window.cpp" line="1352"/> + <location filename="../Window.cpp" line="1347"/> + <source>Interframe blending</source> + <translation>Interframe-Überblendung</translation> + </message> + <message> + <location filename="../Window.cpp" line="1364"/> <source>Frame&amp;skip</source> <translation>Frame&amp;skip</translation> </message> <message> - <location filename="../Window.cpp" line="1365"/> + <location filename="../Window.cpp" line="1377"/> <source>Mute</source> <translation>Stummschalten</translation> </message> <message> - <location filename="../Window.cpp" line="1371"/> + <location filename="../Window.cpp" line="1384"/> <source>FPS target</source> <translation>Bildwiederholrate</translation> </message> <message> - <location filename="../Window.cpp" line="1394"/> + <location filename="../Window.cpp" line="1407"/> <source>Take &amp;screenshot</source> <translation>&amp;Screenshot erstellen</translation> </message> <message> - <location filename="../Window.cpp" line="1396"/> + <location filename="../Window.cpp" line="1409"/> <source>F12</source> <translation>F12</translation> </message> <message> - <location filename="../Window.cpp" line="1404"/> + <location filename="../Window.cpp" line="1417"/> <source>Record GIF...</source> <translation>GIF aufzeichen...</translation> </message> <message> - <location filename="../Window.cpp" line="1276"/> + <location filename="../Window.cpp" line="1279"/> <source>Game Boy Printer...</source> <translation>Game Boy Printer...</translation> </message> <message> - <location filename="../Window.cpp" line="1408"/> + <location filename="../Window.cpp" line="1421"/> <source>Video layers</source> <translation>Video-Ebenen</translation> </message> <message> - <location filename="../Window.cpp" line="1409"/> + <location filename="../Window.cpp" line="1422"/> <source>Audio channels</source> <translation>Audio-Kanäle</translation> </message> <message> - <location filename="../Window.cpp" line="1411"/> + <location filename="../Window.cpp" line="1424"/> <source>Adjust layer placement...</source> <translation>Lage der Bildebenen anpassen...</translation> </message> <message> - <location filename="../Window.cpp" line="1413"/> + <location filename="../Window.cpp" line="1426"/> <source>&amp;Tools</source> <translation>&amp;Werkzeuge</translation> </message> <message> - <location filename="../Window.cpp" line="1414"/> + <location filename="../Window.cpp" line="1427"/> <source>View &amp;logs...</source> <translation>&amp;Logs ansehen...</translation> </message> <message> - <location filename="../Window.cpp" line="1416"/> + <location filename="../Window.cpp" line="1429"/> <source>Game &amp;overrides...</source> <translation>Spiel-&amp;Überschreibungen...</translation> </message> <message> - <location filename="../Window.cpp" line="1428"/> - <source>Game &amp;Pak sensors...</source> - <translation>Game &amp;Pak-Sensoren...</translation> - </message> - <message> - <location filename="../Window.cpp" line="1439"/> + <location filename="../Window.cpp" line="1452"/> <source>&amp;Cheats...</source> <translation>&amp;Cheats...</translation> </message> <message> - <location filename="../Window.cpp" line="1446"/> + <location filename="../Window.cpp" line="1459"/> <source>Open debugger console...</source> <translation>Debugger-Konsole öffnen...</translation> </message> <message> - <location filename="../Window.cpp" line="1448"/> + <location filename="../Window.cpp" line="1461"/> <source>Start &amp;GDB server...</source> <translation>&amp;GDB-Server starten...</translation> </message> <message> - <location filename="../Window.cpp" line="1442"/> + <location filename="../Window.cpp" line="1455"/> <source>Settings...</source> <translation>Einstellungen...</translation> </message>

@@ -3882,62 +4003,72 @@ <source>DS</source>

<translation>DS</translation> </message> <message> - <location filename="../Window.cpp" line="396"/> + <location filename="../Window.cpp" line="361"/> <source>Select folder</source> <translation>Ordner auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="1054"/> + <location filename="../Window.cpp" line="1057"/> <source>Add folder to library...</source> <translation>Ordner zur Bibliothek hinzufügen...</translation> </message> <message> - <location filename="../Window.cpp" line="1164"/> + <location filename="../Window.cpp" line="1167"/> <source>About...</source> <translation>Über...</translation> </message> <message> - <location filename="../Window.cpp" line="1292"/> + <location filename="../Window.cpp" line="1295"/> <source>%1×</source> <translation>%1x</translation> </message> <message> - <location filename="../Window.cpp" line="1344"/> + <location filename="../Window.cpp" line="1356"/> <source>Bilinear filtering</source> <translation>Bilineare Filterung</translation> </message> <message> - <location filename="../Window.cpp" line="1379"/> + <location filename="../Window.cpp" line="1392"/> <source>Native (59.7275)</source> <translation>Nativ (59.7275)</translation> </message> <message> - <location filename="../Window.cpp" line="1400"/> + <location filename="../Window.cpp" line="1413"/> <source>Record A/V...</source> <translation>Audio/Video aufzeichnen...</translation> </message> <message> - <location filename="../Window.cpp" line="1454"/> + <location filename="../Window.cpp" line="1441"/> + <source>Game Pak sensors...</source> + <translation>Spielmodul-Sensoren...</translation> + </message> + <message> + <location filename="../Window.cpp" line="1467"/> <source>View &amp;palette...</source> <translation>&amp;Palette betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1455"/> + <location filename="../Window.cpp" line="1468"/> <source>View &amp;sprites...</source> <translation>&amp;Sprites betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1456"/> + <location filename="../Window.cpp" line="1469"/> <source>View &amp;tiles...</source> <translation>&amp;Tiles betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1457"/> + <location filename="../Window.cpp" line="1470"/> <source>View &amp;map...</source> <translation>&amp;Map betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1458"/> + <location filename="../Window.cpp" line="1473"/> + <source>&amp;Frame inspector...</source> + <translation>&amp;Bildbetrachter...</translation> + </message> + <message> + <location filename="../Window.cpp" line="1477"/> <source>View memory...</source> <translation>Speicher betrachten...</translation> </message>

@@ -3947,87 +4078,87 @@ <source>View &amp;I/O registers...</source>

<translation>&amp;I/O-Register betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1459"/> + <location filename="../Window.cpp" line="1478"/> <source>Search memory...</source> <translation>Speicher durchsuchen...</translation> </message> <message> - <location filename="../Window.cpp" line="1462"/> + <location filename="../Window.cpp" line="1481"/> <source>View &amp;I/O registers...</source> <translation>&amp;I/O-Register betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1467"/> + <location filename="../Window.cpp" line="1486"/> <source>Record debug video log...</source> <translation>Video-Protokoll aufzeichnen...</translation> </message> <message> - <location filename="../Window.cpp" line="1468"/> + <location filename="../Window.cpp" line="1487"/> <source>Stop debug video log</source> <translation>Aufzeichnen des Video-Protokolls beenden</translation> </message> <message> - <location filename="../Window.cpp" line="1549"/> + <location filename="../Window.cpp" line="1568"/> <source>Exit fullscreen</source> <translation>Vollbildmodus beenden</translation> </message> <message> - <location filename="../Window.cpp" line="1551"/> + <location filename="../Window.cpp" line="1570"/> <source>GameShark Button (held)</source> <translation>GameShark-Taste (gehalten)</translation> </message> <message> - <location filename="../Window.cpp" line="1557"/> + <location filename="../Window.cpp" line="1576"/> <source>Autofire</source> <translation>Autofeuer</translation> </message> <message> - <location filename="../Window.cpp" line="1558"/> + <location filename="../Window.cpp" line="1577"/> <source>Autofire A</source> <translation>Autofeuer A</translation> </message> <message> - <location filename="../Window.cpp" line="1563"/> + <location filename="../Window.cpp" line="1582"/> <source>Autofire B</source> <translation>Autofeuer B</translation> </message> <message> - <location filename="../Window.cpp" line="1568"/> + <location filename="../Window.cpp" line="1587"/> <source>Autofire L</source> <translation>Autofeuer L</translation> </message> <message> - <location filename="../Window.cpp" line="1573"/> + <location filename="../Window.cpp" line="1592"/> <source>Autofire R</source> <translation>Autofeuer R</translation> </message> <message> - <location filename="../Window.cpp" line="1578"/> + <location filename="../Window.cpp" line="1597"/> <source>Autofire Start</source> <translation>Autofeuer Start</translation> </message> <message> - <location filename="../Window.cpp" line="1583"/> + <location filename="../Window.cpp" line="1602"/> <source>Autofire Select</source> <translation>Autofeuer Select</translation> </message> <message> - <location filename="../Window.cpp" line="1588"/> + <location filename="../Window.cpp" line="1607"/> <source>Autofire Up</source> <translation>Autofeuer nach oben</translation> </message> <message> - <location filename="../Window.cpp" line="1593"/> + <location filename="../Window.cpp" line="1612"/> <source>Autofire Right</source> <translation>Autofeuer rechts</translation> </message> <message> - <location filename="../Window.cpp" line="1598"/> + <location filename="../Window.cpp" line="1617"/> <source>Autofire Down</source> <translation>Autofeuer nach unten</translation> </message> <message> - <location filename="../Window.cpp" line="1603"/> + <location filename="../Window.cpp" line="1622"/> <source>Autofire Left</source> <translation>Autofeuer links</translation> </message>

@@ -4340,7 +4471,7 @@ <translation>Überspringe</translation>

</message> <message> <location filename="../SettingsView.ui" line="362"/> - <location filename="../SettingsView.ui" line="729"/> + <location filename="../SettingsView.ui" line="736"/> <source>frames</source> <translation>Bild(er)</translation> </message>

@@ -4386,226 +4517,231 @@ <source>Native (59.7275)</source>

<translation>Nativ (59.7275)</translation> </message> <message> - <location filename="../SettingsView.ui" line="469"/> + <location filename="../SettingsView.ui" line="465"/> + <source>Interframe blending</source> + <translation>Interframe-Überblendung</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="476"/> <source>Language</source> <translation>Sprache</translation> </message> <message> - <location filename="../SettingsView.ui" line="477"/> + <location filename="../SettingsView.ui" line="484"/> <source>English</source> <translation>Englisch</translation> </message> <message> - <location filename="../SettingsView.ui" line="500"/> + <location filename="../SettingsView.ui" line="507"/> <source>List view</source> <translation>Listenansicht</translation> </message> <message> - <location filename="../SettingsView.ui" line="505"/> + <location filename="../SettingsView.ui" line="512"/> <source>Tree view</source> <translation>Baumansicht</translation> </message> <message> - <location filename="../SettingsView.ui" line="561"/> + <location filename="../SettingsView.ui" line="568"/> <source>Show FPS in title bar</source> <translation>Bildwiederholrate in der Titelleiste anzeigen</translation> </message> <message> - <location filename="../SettingsView.ui" line="585"/> + <location filename="../SettingsView.ui" line="592"/> <source>Automatically save cheats</source> <translation>Cheats automatisch speichern</translation> </message> <message> - <location filename="../SettingsView.ui" line="595"/> + <location filename="../SettingsView.ui" line="602"/> <source>Automatically load cheats</source> <translation>Cheats automatisch laden</translation> </message> <message> - <location filename="../SettingsView.ui" line="605"/> + <location filename="../SettingsView.ui" line="612"/> <source>Automatically save state</source> <translation>Zustand (Savestate) automatisch speichern</translation> </message> <message> - <location filename="../SettingsView.ui" line="615"/> + <location filename="../SettingsView.ui" line="622"/> <source>Automatically load state</source> <translation>Zustand (Savestate) automatisch laden</translation> </message> <message> - <location filename="../SettingsView.ui" line="625"/> + <location filename="../SettingsView.ui" line="632"/> <source>Enable Discord Rich Presence</source> <translation>Discord-Integration aktivieren</translation> </message> <message> - <location filename="../SettingsView.ui" line="864"/> + <location filename="../SettingsView.ui" line="871"/> <source>Video renderer:</source> <translation>Video-Renderer:</translation> </message> <message> - <location filename="../SettingsView.ui" line="872"/> + <location filename="../SettingsView.ui" line="879"/> <source>Software</source> <translation>Software</translation> </message> <message> - <location filename="../SettingsView.ui" line="877"/> + <location filename="../SettingsView.ui" line="884"/> <source>OpenGL</source> <translation>OpenGL</translation> </message> <message> - <location filename="../SettingsView.ui" line="885"/> + <location filename="../SettingsView.ui" line="892"/> <source>OpenGL enhancements</source> <translation>OpenGL-Verbesserungen</translation> </message> <message> - <location filename="../SettingsView.ui" line="891"/> + <location filename="../SettingsView.ui" line="898"/> <source>High-resolution scale:</source> <translation>Hochauflösende Skalierung:</translation> </message> <message> - <location filename="../SettingsView.ui" line="914"/> + <location filename="../SettingsView.ui" line="921"/> <source>XQ GBA audio (experimental)</source> <translation>XQ GBA-Audio (experimentell)</translation> </message> <message> - <location filename="../SettingsView.ui" line="1253"/> + <location filename="../SettingsView.ui" line="1260"/> <source>Cheats</source> <translation>Cheats</translation> </message> <message> - <location filename="../SettingsView.ui" line="1310"/> + <location filename="../SettingsView.ui" line="1317"/> <source>Log to file</source> <translation>In Datei protokollieren</translation> </message> <message> - <location filename="../SettingsView.ui" line="1317"/> + <location filename="../SettingsView.ui" line="1324"/> <source>Log to console</source> <translation>Auf die Konsole protokollieren</translation> </message> <message> - <location filename="../SettingsView.ui" line="1331"/> + <location filename="../SettingsView.ui" line="1338"/> <source>Select Log File</source> <translation>Protokoll-Datei auswählen</translation> </message> <message> - <location filename="../SettingsView.ui" line="1758"/> + <location filename="../SettingsView.ui" line="1765"/> <source>Camera:</source> <translation>Kamera:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1352"/> - <location filename="../SettingsView.ui" line="1388"/> - <location filename="../SettingsView.ui" line="1424"/> + <location filename="../SettingsView.ui" line="1359"/> + <location filename="../SettingsView.ui" line="1395"/> + <location filename="../SettingsView.ui" line="1431"/> <source>Autodetect</source> <translation>Automatisch erkennen</translation> </message> <message> - <location filename="../SettingsView.ui" line="1357"/> - <location filename="../SettingsView.ui" line="1393"/> - <location filename="../SettingsView.ui" line="1429"/> + <location filename="../SettingsView.ui" line="1364"/> + <location filename="../SettingsView.ui" line="1400"/> + <location filename="../SettingsView.ui" line="1436"/> <source>Game Boy (DMG)</source> <translation>Game Boy (DMG)</translation> </message> <message> - <location filename="../SettingsView.ui" line="1362"/> - <location filename="../SettingsView.ui" line="1398"/> - <location filename="../SettingsView.ui" line="1434"/> + <location filename="../SettingsView.ui" line="1369"/> + <location filename="../SettingsView.ui" line="1405"/> + <location filename="../SettingsView.ui" line="1441"/> <source>Super Game Boy (SGB)</source> <translation>Super Game Boy (SGB)</translation> </message> <message> - <location filename="../SettingsView.ui" line="1367"/> - <location filename="../SettingsView.ui" line="1403"/> - <location filename="../SettingsView.ui" line="1439"/> + <location filename="../SettingsView.ui" line="1374"/> + <location filename="../SettingsView.ui" line="1410"/> + <location filename="../SettingsView.ui" line="1446"/> <source>Game Boy Color (CGB)</source> <translation>Game Boy Color (CGB)</translation> </message> <message> - <location filename="../SettingsView.ui" line="1372"/> - <location filename="../SettingsView.ui" line="1408"/> - <location filename="../SettingsView.ui" line="1444"/> + <location filename="../SettingsView.ui" line="1379"/> + <location filename="../SettingsView.ui" line="1415"/> + <location filename="../SettingsView.ui" line="1451"/> <source>Game Boy Advance (AGB)</source> <translation>Game Boy Advance (AGB)</translation> </message> <message> - <location filename="../SettingsView.ui" line="1459"/> + <location filename="../SettingsView.ui" line="1466"/> <source>Default BG colors:</source> <translation>Standard-Hintergrundfarben:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1737"/> + <location filename="../SettingsView.ui" line="1744"/> <source>Default sprite colors 1:</source> <translation>Standard-Sprite-Farben 1:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1744"/> + <location filename="../SettingsView.ui" line="1751"/> <source>Default sprite colors 2:</source> <translation>Standard-Sprite-Farben 2:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1751"/> + <location filename="../SettingsView.ui" line="1758"/> <source>Use GBC colors in GB games</source> <translation>Verwende GBC-Farben in GB-Spielen</translation> </message> <message> - <location filename="../SettingsView.ui" line="1626"/> + <location filename="../SettingsView.ui" line="1633"/> <source>Super Game Boy borders</source> <translation>Super Game Boy-Rahmen</translation> </message> <message> - <location filename="../SettingsView.ui" line="1344"/> + <location filename="../SettingsView.ui" line="1351"/> <source>Game Boy model:</source> <translation>Game Boy-Modell:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1380"/> + <location filename="../SettingsView.ui" line="1387"/> <source>Super Game Boy model:</source> <translation>Super Game Boy-Modell:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1416"/> + <location filename="../SettingsView.ui" line="1423"/> <source>Game Boy Color model:</source> <translation>Game Boy Color-Modell:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1640"/> + <location filename="../SettingsView.ui" line="1647"/> <source>Camera driver:</source> <translation>Kamera-Treiber:</translation> </message> <message> - <location filename="../SettingsView.ui" line="492"/> + <location filename="../SettingsView.ui" line="499"/> <source>Library:</source> <translation>Bibliothek:</translation> </message> <message> - <location filename="../SettingsView.ui" line="513"/> + <location filename="../SettingsView.ui" line="520"/> <source>Show when no game open</source> <translation>Anzeigen, wenn kein Spiel geöffnet ist</translation> </message> <message> - <location filename="../SettingsView.ui" line="523"/> + <location filename="../SettingsView.ui" line="530"/> <source>Clear cache</source> <translation>Cache leeren</translation> </message> <message> - <location filename="../SettingsView.ui" line="639"/> + <location filename="../SettingsView.ui" line="646"/> <source>Fast forward speed:</source> <translation>Vorlauf-Geschwindigkeit:</translation> </message> <message> - <location filename="../SettingsView.ui" line="771"/> + <location filename="../SettingsView.ui" line="778"/> <source>Preload entire ROM into memory</source> <translation>ROM-Datei vollständig in Arbeitsspeicher vorladen</translation> </message> <message> - <location filename="../SettingsView.ui" line="944"/> - <location filename="../SettingsView.ui" line="982"/> - <location filename="../SettingsView.ui" line="1017"/> - <location filename="../SettingsView.ui" line="1045"/> - <location filename="../SettingsView.ui" line="1086"/> - <location filename="../SettingsView.ui" line="1134"/> - <location filename="../SettingsView.ui" line="1182"/> - <location filename="../SettingsView.ui" line="1230"/> - <location filename="../SettingsView.ui" line="1278"/> + <location filename="../SettingsView.ui" line="951"/> + <location filename="../SettingsView.ui" line="989"/> + <location filename="../SettingsView.ui" line="1024"/> + <location filename="../SettingsView.ui" line="1052"/> + <location filename="../SettingsView.ui" line="1093"/> + <location filename="../SettingsView.ui" line="1141"/> + <location filename="../SettingsView.ui" line="1189"/> + <location filename="../SettingsView.ui" line="1237"/> + <location filename="../SettingsView.ui" line="1285"/> <source>Browse</source> <translation>Durchsuchen</translation> </message>

@@ -4620,29 +4756,29 @@ <source>DS BIOS 9 file:</source>

<translation>DS-BIOS 9:</translation> </message> <message> - <location filename="../SettingsView.ui" line="953"/> + <location filename="../SettingsView.ui" line="960"/> <source>Use BIOS file if found</source> <translation>BIOS-Datei verwenden, wenn vorhanden</translation> </message> <message> - <location filename="../SettingsView.ui" line="963"/> + <location filename="../SettingsView.ui" line="970"/> <source>Skip BIOS intro</source> <translation>BIOS-Intro überspringen</translation> </message> <message> - <location filename="../SettingsView.ui" line="651"/> - <location filename="../SettingsView.ui" line="898"/> + <location filename="../SettingsView.ui" line="658"/> + <location filename="../SettingsView.ui" line="905"/> <source>×</source> <translation>×</translation> </message> <message> - <location filename="../SettingsView.ui" line="670"/> + <location filename="../SettingsView.ui" line="677"/> <source>Unbounded</source> <translation>unbegrenzt</translation> </message> <message> - <location filename="../SettingsView.ui" line="544"/> + <location filename="../SettingsView.ui" line="551"/> <source>Suspend screensaver</source> <translation>Bildschirmschoner deaktivieren</translation> </message>

@@ -4652,50 +4788,50 @@ <source>BIOS</source>

<translation>BIOS</translation> </message> <message> - <location filename="../SettingsView.ui" line="554"/> + <location filename="../SettingsView.ui" line="561"/> <source>Pause when inactive</source> <translation>Pause, wenn inaktiv</translation> </message> <message> - <location filename="../SettingsView.ui" line="753"/> + <location filename="../SettingsView.ui" line="760"/> <source>Run all</source> <translation>Alle ausführen</translation> </message> <message> - <location filename="../SettingsView.ui" line="758"/> + <location filename="../SettingsView.ui" line="765"/> <source>Remove known</source> <translation>Bekannte entfernen</translation> </message> <message> - <location filename="../SettingsView.ui" line="763"/> + <location filename="../SettingsView.ui" line="770"/> <source>Detect and remove</source> <translation>Erkennen und entfernen</translation> </message> <message> - <location filename="../SettingsView.ui" line="537"/> + <location filename="../SettingsView.ui" line="544"/> <source>Allow opposing input directions</source> <translation>Gegensätzliche Eingaberichtungen erlauben</translation> </message> <message> - <location filename="../SettingsView.ui" line="792"/> - <location filename="../SettingsView.ui" line="836"/> + <location filename="../SettingsView.ui" line="799"/> + <location filename="../SettingsView.ui" line="843"/> <source>Screenshot</source> <translation>Screenshot</translation> </message> <message> - <location filename="../SettingsView.ui" line="802"/> - <location filename="../SettingsView.ui" line="846"/> + <location filename="../SettingsView.ui" line="809"/> + <location filename="../SettingsView.ui" line="853"/> <source>Save data</source> <translation>Speicherdaten</translation> </message> <message> - <location filename="../SettingsView.ui" line="812"/> - <location filename="../SettingsView.ui" line="853"/> + <location filename="../SettingsView.ui" line="819"/> + <location filename="../SettingsView.ui" line="860"/> <source>Cheat codes</source> <translation>Cheat-Codes</translation> </message> <message> - <location filename="../SettingsView.ui" line="706"/> + <location filename="../SettingsView.ui" line="713"/> <source>Enable rewind</source> <translation>Rücklauf aktivieren</translation> </message>

@@ -4705,42 +4841,42 @@ <source>Bilinear filtering</source>

<translation>Bilineare Filterung</translation> </message> <message> - <location filename="../SettingsView.ui" line="713"/> + <location filename="../SettingsView.ui" line="720"/> <source>Rewind history:</source> <translation>Rücklauf-Verlauf:</translation> </message> <message> - <location filename="../SettingsView.ui" line="745"/> + <location filename="../SettingsView.ui" line="752"/> <source>Idle loops:</source> <translation>Leerlaufprozesse:</translation> </message> <message> - <location filename="../SettingsView.ui" line="785"/> + <location filename="../SettingsView.ui" line="792"/> <source>Savestate extra data:</source> <translation>Zusätzliche Savestate-Daten:</translation> </message> <message> - <location filename="../SettingsView.ui" line="829"/> + <location filename="../SettingsView.ui" line="836"/> <source>Load extra data:</source> <translation>Lade zusätzliche Daten:</translation> </message> <message> - <location filename="../SettingsView.ui" line="682"/> + <location filename="../SettingsView.ui" line="689"/> <source>Autofire interval:</source> <translation>Autofeuer-Intervall:</translation> </message> <message> - <location filename="../SettingsView.ui" line="925"/> + <location filename="../SettingsView.ui" line="932"/> <source>GB BIOS file:</source> <translation>Datei mit GB-BIOS:</translation> </message> <message> - <location filename="../SettingsView.ui" line="991"/> + <location filename="../SettingsView.ui" line="998"/> <source>GBA BIOS file:</source> <translation>Datei mit GBA-BIOS:</translation> </message> <message> - <location filename="../SettingsView.ui" line="998"/> + <location filename="../SettingsView.ui" line="1005"/> <source>GBC BIOS file:</source> <translation>Datei mit GBC-BIOS:</translation> </message>

@@ -4750,36 +4886,36 @@ <source>DS firmware file:</source>

<translation>DS-Firmware:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1026"/> + <location filename="../SettingsView.ui" line="1033"/> <source>SGB BIOS file:</source> <translation>Datei mit SGB-BIOS:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1061"/> + <location filename="../SettingsView.ui" line="1068"/> <source>Save games</source> <translation>Spielstände</translation> </message> <message> - <location filename="../SettingsView.ui" line="1095"/> - <location filename="../SettingsView.ui" line="1143"/> - <location filename="../SettingsView.ui" line="1191"/> - <location filename="../SettingsView.ui" line="1239"/> - <location filename="../SettingsView.ui" line="1287"/> + <location filename="../SettingsView.ui" line="1102"/> + <location filename="../SettingsView.ui" line="1150"/> + <location filename="../SettingsView.ui" line="1198"/> + <location filename="../SettingsView.ui" line="1246"/> + <location filename="../SettingsView.ui" line="1294"/> <source>Same directory as the ROM</source> <translation>Verzeichnis der ROM-Datei</translation> </message> <message> - <location filename="../SettingsView.ui" line="1109"/> + <location filename="../SettingsView.ui" line="1116"/> <source>Save states</source> <translation>Savestates</translation> </message> <message> - <location filename="../SettingsView.ui" line="1157"/> + <location filename="../SettingsView.ui" line="1164"/> <source>Screenshots</source> <translation>Screenshots</translation> </message> <message> - <location filename="../SettingsView.ui" line="1205"/> + <location filename="../SettingsView.ui" line="1212"/> <source>Patches</source> <translation>Patches</translation> </message>
M src/platform/switch/main.csrc/platform/switch/main.c

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "feature/gui/gui-runner.h" #include <mgba/core/blip_buf.h> #include <mgba/core/core.h> +#include <mgba/internal/gb/video.h> #include <mgba/internal/gba/audio.h> #include <mgba/internal/gba/input.h> #include <mgba-util/gui.h>

@@ -99,6 +100,9 @@ static u8 vmode;

static u32 vwidth; static u32 vheight; static bool interframeBlending = false; +static bool sgbCrop = false; +static bool useLightSensor = true; +static struct mGUIRunnerLux lightSensor; static enum ScreenMode { SM_PA,

@@ -268,6 +272,10 @@ runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d);

runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation); runner->core->setAVStream(runner->core, &stream); + if (runner->core->platform(runner->core) == PLATFORM_GBA && useLightSensor) { + runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d); + } + unsigned mode; if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) { screenMode = mode;

@@ -292,6 +300,22 @@ int fakeBool;

if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { interframeBlending = fakeBool; } + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } + if (mCoreConfigGetIntValue(&runner->config, "useLightSensor", &fakeBool)) { + if (useLightSensor != fakeBool) { + useLightSensor = fakeBool; + + if (runner->core->platform(runner->core) == PLATFORM_GBA) { + if (useLightSensor) { + runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d); + } else { + runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &runner->luminanceSource.d); + } + } + } + } rumble.up = 0; rumble.down = 0;

@@ -313,8 +337,14 @@ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glUseProgram(program); glBindVertexArray(vao); - float aspectX = width / (float) vwidth; - float aspectY = height / (float) vheight; + float inwidth = width; + float inheight = height; + if (sgbCrop && width == 256 && height == 224) { + inwidth = GB_VIDEO_HORIZONTAL_PIXELS; + inheight = GB_VIDEO_VERTICAL_PIXELS; + } + float aspectX = inwidth / vwidth; + float aspectY = inheight / vheight; float max = 1.f; switch (screenMode) { case SM_PA:

@@ -340,6 +370,11 @@ aspectY = 1.0;

break; } + if (screenMode != SM_SF) { + aspectX = width / (float) vwidth; + aspectY = height / (float) vheight; + } + aspectX *= max; aspectY *= max;

@@ -543,6 +578,18 @@ hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);

return sixaxis.gyroscope.z * -1.1e9f; } +static void _lightSensorSample(struct GBALuminanceSource* lux) { + struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux; + float luxLevel = 0; + appletGetCurrentIlluminance(&luxLevel); + runnerLux->luxLevel = cbrtf(luxLevel) * 8; +} + +static uint8_t _lightSensorRead(struct GBALuminanceSource* lux) { + struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux; + return 0xFF - runnerLux->luxLevel; +} + static int _batteryState(void) { u32 charge; int state = 0;

@@ -690,6 +737,9 @@ rotation.readTiltX = _readTiltX;

rotation.readTiltY = _readTiltY; rotation.readGyroZ = _readGyroZ; + lightSensor.d.readLuminance = _lightSensorRead; + lightSensor.d.sample = _lightSensorSample; + stream.videoDimensionsChanged = NULL; stream.postVideoFrame = NULL; stream.postAudioFrame = NULL;

@@ -706,6 +756,9 @@ audoutBuffer[i].buffer_size = BUFFER_SIZE;

audoutBuffer[i].data_size = BUFFER_SIZE; audoutBuffer[i].data_offset = 0; } + + bool illuminanceAvailable = false; + appletIsIlluminanceAvailable(&illuminanceAvailable); struct mGUIRunner runner = { .params = {

@@ -829,8 +882,19 @@ GUI_V_U(6),

}, .nStates = 6 }, + { + .title = "Use built-in brightness sensor for Boktai", + .data = "useLightSensor", + .submenu = 0, + .state = illuminanceAvailable, + .validStates = (const char*[]) { + "Off", + "On", + }, + .nStates = 2 + }, }, - .nConfigExtra = 4, + .nConfigExtra = 5, .setup = _setup, .teardown = NULL, .gameLoaded = _gameLoaded,
M src/platform/wii/main.csrc/platform/wii/main.c

@@ -17,6 +17,7 @@

#include <mgba/core/blip_buf.h> #include <mgba/core/core.h> #include "feature/gui/gui-runner.h" +#include <mgba/internal/gb/video.h> #include <mgba/internal/gba/audio.h> #include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/input.h>

@@ -113,6 +114,7 @@ static uint16_t* rescaleTexmem;

static GXTexObj rescaleTex; static uint16_t* interframeTexmem; static GXTexObj interframeTex; +static bool sgbCrop = false; static int32_t tiltX; static int32_t tiltY; static int32_t gyroZ;

@@ -862,6 +864,9 @@ int fakeBool;

if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { interframeBlending = fakeBool; } + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } float stretch; if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) {

@@ -952,20 +957,25 @@ GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);

} } - int hfactor = (vmode->fbWidth * wStretch) / (corew * wAdjust); - int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust); - if (hfactor > vfactor) { - scaleFactor = vfactor; - } else { - scaleFactor = hfactor; - } + if (screenMode == SM_PA) { + unsigned factorWidth = corew; + unsigned factorHeight = coreh; + if (sgbCrop && factorWidth == 256 && factorHeight == 224) { + factorWidth = GB_VIDEO_HORIZONTAL_PIXELS; + factorHeight = GB_VIDEO_VERTICAL_PIXELS; + } + + int hfactor = (vmode->fbWidth * wStretch) / (factorWidth * wAdjust); + int vfactor = (vmode->efbHeight * hStretch) / (factorHeight * hAdjust); + if (hfactor > vfactor) { + scaleFactor = vfactor; + } else { + scaleFactor = hfactor; + } - if (screenMode == SM_PA) { vertWidth *= scaleFactor; vertHeight *= scaleFactor; - } - if (screenMode == SM_PA) { _reproj(corew * scaleFactor, coreh * scaleFactor); } else { _reproj2(corew, coreh);
M src/util/gui/file-select.csrc/util/gui/file-select.c

@@ -47,7 +47,7 @@ static int _strpcmp(const void* a, const void* b) {

return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title); } -static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)) { +static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) { _cleanFiles(currentFiles); struct VDir* dir = VDirOpen(currentPath);

@@ -144,6 +144,9 @@ if (failed) {

free((char*) testItem->title); GUIMenuItemListShift(currentFiles, item, 1); } else { + if (preselect && strncmp(testItem->title, preselect, PATH_MAX) == 0) { + params->fileIndex = item; + } ++item; } }

@@ -152,14 +155,14 @@

return true; } -bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)) { +bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) { struct GUIMenu menu = { .title = "Select file", .subtitle = params->currentPath, - .index = params->fileIndex, }; GUIMenuItemListInit(&menu.items, 0); - _refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents); + _refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, preselect); + menu.index = params->fileIndex; while (true) { struct GUIMenuItem* item;

@@ -174,7 +177,7 @@ if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {

continue; } _upDirectory(params->currentPath); - if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) { + if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) { break; } } else {

@@ -187,7 +190,7 @@ snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title);

struct GUIMenuItemList newFiles; GUIMenuItemListInit(&newFiles, 0); - if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents)) { + if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents, NULL)) { _cleanFiles(&newFiles); GUIMenuItemListDeinit(&newFiles); _cleanFiles(&menu.items);

@@ -208,7 +211,7 @@ if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {

break; } _upDirectory(params->currentPath); - if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) { + if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) { break; } params->fileIndex = 0;