From e0b07a644628155790efe60fb038158569e090d2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 3 Sep 2022 06:48:09 -0700 Subject: [PATCH 01/37] GB: Fix HALT breaking M-cycle alignment (fixes #250) --- CHANGES | 1 + src/gb/gb.c | 77 ++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/CHANGES b/CHANGES index 7dbcf3446..5c1599a20 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,7 @@ Features: Emulation fixes: - ARM7: Fix unsigned multiply timing - GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378) + - GB: Fix HALT breaking M-cycle alignment (fixes mgba.io/i/250) - GB Audio: Fix channel 1/2 reseting edge cases (fixes mgba.io/i/1925) - GB Audio: Properly apply per-model audio differences - GB Audio: Revamp channel rendering diff --git a/src/gb/gb.c b/src/gb/gb.c index e2459e8fe..2f1fe3ba9 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -844,39 +844,67 @@ void GBUpdateIRQs(struct GB* gb) { SM83RaiseIRQ(gb->cpu); } +static void _GBAdvanceCycles(struct GB* gb) { + struct SM83Core* cpu = gb->cpu; + int stateMask = (4 * (2 - gb->doubleSpeed)) - 1; + int stateOffset = ((cpu->nextEvent - cpu->cycles) & stateMask) >> !gb->doubleSpeed; + cpu->cycles = cpu->nextEvent; + cpu->executionState = (cpu->executionState + stateOffset) & 3; +} + void GBProcessEvents(struct SM83Core* cpu) { struct GB* gb = (struct GB*) cpu->master; - do { - int32_t cycles = cpu->cycles; - int32_t nextEvent; - - cpu->cycles = 0; - cpu->nextEvent = INT_MAX; - - nextEvent = cycles; - do { -#ifdef USE_DEBUGGERS - gb->timing.globalCycles += nextEvent; +#ifndef NDEBUG + int stateMask = (4 * (2 - gb->doubleSpeed)) - 1; + int state = (mTimingGlobalTime(&gb->timing) & stateMask) >> !gb->doubleSpeed; + if (((state + 3) & 3) != (cpu->executionState & 3)) { + mLOG(GB, ERROR, "T-states and M-cycles became misaligned"); + } #endif - nextEvent = mTimingTick(&gb->timing, nextEvent); - } while (gb->cpuBlocked); - // This loop cannot early exit until the SM83 run loop properly handles mid-M-cycle-exits - cpu->nextEvent = nextEvent; + bool wasHalted = cpu->halted; + while (true) { + do { + int32_t cycles = cpu->cycles; + int32_t nextEvent; - if (cpu->halted) { - cpu->cycles = cpu->nextEvent; - if (!gb->memory.ie || !gb->memory.ime) { + cpu->cycles = 0; + cpu->nextEvent = INT_MAX; + + nextEvent = cycles; + do { +#ifdef USE_DEBUGGERS + gb->timing.globalCycles += nextEvent; +#endif + nextEvent = mTimingTick(&gb->timing, nextEvent); + } while (gb->cpuBlocked); + // This loop cannot early exit until the SM83 run loop properly handles mid-M-cycle-exits + cpu->nextEvent = nextEvent; + + if (cpu->halted) { + _GBAdvanceCycles(gb); + if (!gb->memory.ie || !gb->memory.ime) { + break; + } + } + if (gb->earlyExit) { break; } + } while (cpu->cycles >= cpu->nextEvent); + if (gb->cpuBlocked) { + _GBAdvanceCycles(gb); } - if (gb->earlyExit) { + if (!wasHalted || (cpu->executionState & 3) == SM83_CORE_FETCH) { break; } - } while (cpu->cycles >= cpu->nextEvent); - gb->earlyExit = false; - if (gb->cpuBlocked) { - cpu->cycles = cpu->nextEvent; + int nextFetch = (SM83_CORE_FETCH - cpu->executionState) * cpu->tMultiplier; + if (nextFetch < cpu->nextEvent) { + cpu->cycles += nextFetch; + cpu->executionState = SM83_CORE_FETCH; + break; + } + _GBAdvanceCycles(gb); } + gb->earlyExit = false; } void GBSetInterrupts(struct SM83Core* cpu, bool enable) { @@ -928,7 +956,8 @@ static void _enableInterrupts(struct mTiming* timing, void* user, uint32_t cycle void GBHalt(struct SM83Core* cpu) { struct GB* gb = (struct GB*) cpu->master; if (!(gb->memory.ie & gb->memory.io[GB_REG_IF] & 0x1F)) { - cpu->cycles = cpu->nextEvent; + _GBAdvanceCycles(gb); + cpu->executionState = (cpu->executionState - 1) & 3; cpu->halted = true; } else if (!gb->memory.ime) { mLOG(GB, GAME_ERROR, "HALT bug"); From 4b0ea00ba1b366d92d51aa1cfb101476153378bb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 7 Sep 2022 20:26:35 -0700 Subject: [PATCH 02/37] GBA: Fix 1 MiB ROM mirroring to only mirror 4 times --- CHANGES | 1 + src/gba/gba.c | 17 ++++++++++++++--- src/gba/memory.c | 12 ------------ src/gba/overrides.c | 5 ----- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/CHANGES b/CHANGES index 5c1599a20..afb21e008 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,7 @@ Emulation fixes: - GBA: Improve timing when not booting from BIOS - GBA: Fix expected entry point for multiboot ELFs (fixes mgba.io/i/2450) - GBA: Fix booting multiboot ROMs with no JOY entrypoint + - GBA: Fix 1 MiB ROM mirroring to only mirror 4 times - GBA Audio: Adjust PSG sampling rate with SOUNDBIAS - GBA Audio: Sample FIFOs at SOUNDBIAS-set frequency - GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059) diff --git a/src/gba/gba.c b/src/gba/gba.c index a6df59f4c..70da8e345 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -364,7 +364,6 @@ bool GBALoadNull(struct GBA* gba) { gba->yankedRomSize = 0; gba->memory.romSize = SIZE_CART0; gba->memory.romMask = SIZE_CART0 - 1; - gba->memory.mirroring = false; gba->romCrc32 = 0; if (gba->cpu) { @@ -419,6 +418,19 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) { gba->memory.romSize = SIZE_CART0; } gba->pristineRomSize = SIZE_CART0; + } else if (gba->pristineRomSize == 0x00100000) { + // 1 MiB ROMs (e.g. Classic NES) all appear as 4x mirrored, but not more + gba->isPristine = false; + gba->memory.romSize = 0x00400000; +#ifdef FIXED_ROM_BUFFER + gba->memory.rom = romBuffer; +#else + gba->memory.rom = anonymousMemoryMap(SIZE_CART0); +#endif + vf->read(vf, gba->memory.rom, gba->pristineRomSize); + memcpy(&gba->memory.rom[0x40000], gba->memory.rom, 0x00100000); + memcpy(&gba->memory.rom[0x80000], gba->memory.rom, 0x00100000); + memcpy(&gba->memory.rom[0xC0000], gba->memory.rom, 0x00100000); } else { gba->memory.rom = vf->map(vf, gba->pristineRomSize, MAP_READ); gba->memory.romSize = gba->pristineRomSize; @@ -430,8 +442,7 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) { } gba->yankedRomSize = 0; gba->memory.romMask = toPow2(gba->memory.romSize) - 1; - gba->memory.mirroring = false; - gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize); + gba->romCrc32 = doCrc32(gba->memory.rom, gba->pristineRomSize); if (popcount32(gba->memory.romSize) != 1) { // This ROM is either a bad dump or homebrew. Emulate flash cart behavior. #ifndef FIXED_ROM_BUFFER diff --git a/src/gba/memory.c b/src/gba/memory.c index 1878e7bb2..c9a1f59dd 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -83,7 +83,6 @@ void GBAMemoryInit(struct GBA* gba) { cpu->memory.activeNonseqCycles32 = 0; cpu->memory.activeNonseqCycles16 = 0; gba->memory.biosPrefetch = 0; - gba->memory.mirroring = false; gba->memory.agbPrintProtect = 0; memset(&gba->memory.agbPrintCtx, 0, sizeof(gba->memory.agbPrintCtx)); @@ -277,9 +276,6 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { if (newRegion < REGION_CART0 || (address & (SIZE_CART0 - 1)) < memory->romSize) { return; } - if (memory->mirroring && (address & memory->romMask) < memory->romSize) { - return; - } } if (memory->activeRegion == REGION_BIOS) { @@ -411,8 +407,6 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { wait += waitstatesRegion[address >> BASE_OFFSET]; \ if ((address & (SIZE_CART0 - 1)) < memory->romSize) { \ LOAD_32(value, address & (SIZE_CART0 - 4), memory->rom); \ - } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { \ - LOAD_32(value, address & memory->romMask & -4, memory->rom); \ } else if (memory->vfame.cartType) { \ value = GBAVFameGetPatternValue(address, 32); \ } else { \ @@ -578,8 +572,6 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if ((address & (SIZE_CART0 - 1)) < memory->romSize) { LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom); - } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { - LOAD_16(value, address & memory->romMask, memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); } else if ((address & (SIZE_CART0 - 1)) >= AGB_PRINT_BASE) { @@ -605,8 +597,6 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = GBACartEReaderRead(&memory->ereader, address); } else if ((address & (SIZE_CART0 - 1)) < memory->romSize) { LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom); - } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { - LOAD_16(value, address & memory->romMask, memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); } else { @@ -698,8 +688,6 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if ((address & (SIZE_CART0 - 1)) < memory->romSize) { value = ((uint8_t*) memory->rom)[address & (SIZE_CART0 - 1)]; - } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { - value = ((uint8_t*) memory->rom)[address & memory->romMask]; } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 8); } else { diff --git a/src/gba/overrides.c b/src/gba/overrides.c index 823957d5c..a0593a90e 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -229,7 +229,6 @@ bool GBAOverrideFind(const struct Configuration* config, struct GBACartridgeOver if (!found && override->id[0] == 'F') { // Classic NES Series override->savetype = SAVEDATA_EEPROM; - override->mirroring = true; found = true; } @@ -377,10 +376,6 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri gba->idleOptimization = IDLE_LOOP_REMOVE; } } - - if (override->mirroring) { - gba->memory.mirroring = true; - } } void GBAOverrideApplyDefaults(struct GBA* gba, const struct Configuration* overrides) { From d95d982ba35795b31ae3384f7d991b56e3d25af9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 9 Sep 2022 01:45:33 -0700 Subject: [PATCH 03/37] Qt: Don't re-enable updates on xcb --- src/platform/qt/DisplayGL.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 4999b8957..16dece1b4 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -298,7 +298,9 @@ void DisplayGL::pauseDrawing() { if (m_hasStarted) { m_isDrawing = false; QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection); - setUpdatesEnabled(true); + if (QGuiApplication::platformName() != "xcb") { + setUpdatesEnabled(true); + } } } From 550866fcaca57486bd930f84ae9038439f4f3f01 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 9 Sep 2022 02:26:20 -0700 Subject: [PATCH 04/37] Libretro: Fix backwards, oversensitive accelerometer (fixes #2635) --- src/platform/libretro/libretro.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 3c5b278bb..9bbd55ae6 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -1359,8 +1359,8 @@ static void _updateRotation(struct mRotationSource* source) { gyroZ = 0; _initSensors(); if (tiltEnabled) { - tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * 3e8f; - tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * -3e8f; + tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * -2e8f; + tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * 2e8f; } if (gyroEnabled) { gyroZ = sensorGetCallback(0, RETRO_SENSOR_GYROSCOPE_Z) * -1.1e9f; From e87f7b7b6885c63ad35ac4f6d0cd4d74e1d36d70 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 9 Sep 2022 16:37:56 -0700 Subject: [PATCH 05/37] Qt: Use FFmpeg to convert additional camera formats, if available --- CHANGES | 1 + src/platform/qt/VideoDumper.cpp | 68 +++++++++++++++++++++++++++++++++ src/platform/qt/VideoDumper.h | 13 +++++++ 3 files changed, 82 insertions(+) diff --git a/CHANGES b/CHANGES index afb21e008..8e02fa02e 100644 --- a/CHANGES +++ b/CHANGES @@ -106,6 +106,7 @@ Misc: - Qt: Boot both a multiboot image and ROM with CLI args (closes mgba.io/i/1941) - Qt: Improve cheat parsing (fixes mgba.io/i/2297) - Qt: Change lossless setting to use WavPack audio + - Qt: Use FFmpeg to convert additional camera formats, if available - SDL: Support exposing an axis directly as the gyro value (closes mgba.io/i/2531) - Windows: Attach to console if present - Vita: Add bilinear filtering option (closes mgba.io/i/344) diff --git a/src/platform/qt/VideoDumper.cpp b/src/platform/qt/VideoDumper.cpp index 392030277..7c5345c55 100644 --- a/src/platform/qt/VideoDumper.cpp +++ b/src/platform/qt/VideoDumper.cpp @@ -23,6 +23,9 @@ bool VideoDumper::present(const QVideoFrame& frame) { QVideoFrame::PixelFormat vFormat = mappedFrame.pixelFormat(); QImage::Format format = QVideoFrame::imageFormatFromPixelFormat(vFormat); bool swap = false; +#ifdef USE_FFMPEG + bool useScaler = false; +#endif if (format == QImage::Format_Invalid) { if (vFormat < QVideoFrame::Format_BGRA5658_Premultiplied) { vFormat = static_cast(vFormat - QVideoFrame::Format_BGRA32 + QVideoFrame::Format_ARGB32); @@ -34,11 +37,68 @@ bool VideoDumper::present(const QVideoFrame& frame) { } swap = true; } else { +#ifdef USE_FFMPEG + enum AVPixelFormat pixelFormat; + switch (vFormat) { + case QVideoFrame::Format_YUV420P: + pixelFormat = AV_PIX_FMT_YUV420P; + break; + case QVideoFrame::Format_YUV422P: + pixelFormat = AV_PIX_FMT_YUV422P; + break; + case QVideoFrame::Format_YUYV: + pixelFormat = AV_PIX_FMT_YUYV422; + break; + case QVideoFrame::Format_UYVY: + pixelFormat = AV_PIX_FMT_UYVY422; + break; + case QVideoFrame::Format_NV12: + pixelFormat = AV_PIX_FMT_NV12; + break; + case QVideoFrame::Format_NV21: + pixelFormat = AV_PIX_FMT_NV12; + break; + default: + return false; + } + format = QImage::Format_RGB888; + useScaler = true; + if (pixelFormat != m_pixfmt || m_scalerSize != mappedFrame.size()) { + if (m_scaler) { + sws_freeContext(m_scaler); + } + m_scaler = sws_getContext(mappedFrame.width(), mappedFrame.height(), pixelFormat, + mappedFrame.width(), mappedFrame.height(), AV_PIX_FMT_RGB24, + SWS_POINT, nullptr, nullptr, nullptr); + m_scalerSize = mappedFrame.size(); + m_pixfmt = pixelFormat; + } +#else return false; +#endif } } uchar* bits = mappedFrame.bits(); +#ifdef USE_FFMPEG + QImage image; + if (!useScaler) { + image = QImage(bits, mappedFrame.width(), mappedFrame.height(), mappedFrame.bytesPerLine(), format); + } + if (useScaler) { + image = QImage(mappedFrame.width(), mappedFrame.height(), format); + const uint8_t* planes[8] = {0}; + int strides[8] = {0}; + for (int plane = 0; plane < mappedFrame.planeCount(); ++plane) { + planes[plane] = mappedFrame.bits(plane); + strides[plane] = mappedFrame.bytesPerLine(plane); + } + uint8_t* outBits = image.bits(); + int outStride = image.bytesPerLine(); + sws_scale(m_scaler, planes, strides, 0, mappedFrame.height(), &outBits, &outStride); + } else +#else QImage image(bits, mappedFrame.width(), mappedFrame.height(), mappedFrame.bytesPerLine(), format); +#endif if (swap) { image = image.rgbSwapped(); } else if (surfaceFormat().scanLineDirection() != QVideoSurfaceFormat::BottomToTop) { @@ -66,5 +126,13 @@ QList VideoDumper::supportedPixelFormats(QAbstractVide list.append(QVideoFrame::Format_BGRA32_Premultiplied); list.append(QVideoFrame::Format_BGR565); list.append(QVideoFrame::Format_BGR555); +#ifdef USE_FFMPEG + list.append(QVideoFrame::Format_YUYV); + list.append(QVideoFrame::Format_UYVY); + list.append(QVideoFrame::Format_YUV422P); + list.append(QVideoFrame::Format_YUV420P); + list.append(QVideoFrame::Format_NV12); + list.append(QVideoFrame::Format_NV21); +#endif return list; } diff --git a/src/platform/qt/VideoDumper.h b/src/platform/qt/VideoDumper.h index 081fe3b45..c22688c2b 100644 --- a/src/platform/qt/VideoDumper.h +++ b/src/platform/qt/VideoDumper.h @@ -7,6 +7,12 @@ #include +#ifdef USE_FFMPEG +extern "C" { +#include +} +#endif + namespace QGBA { class VideoDumper : public QAbstractVideoSurface { @@ -20,6 +26,13 @@ public: signals: void imageAvailable(const QImage& image); + +private: +#ifdef USE_FFMPEG + AVPixelFormat m_pixfmt = AV_PIX_FMT_NONE; + SwsContext* m_scaler = nullptr; + QSize m_scalerSize; +#endif }; } From 29e1ddbb21951cb7ad55bd3974de7f31323c73ef Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 9 Sep 2022 18:29:00 -0700 Subject: [PATCH 06/37] Qt: Fix build on older Qt --- src/platform/qt/VideoDumper.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/qt/VideoDumper.cpp b/src/platform/qt/VideoDumper.cpp index 7c5345c55..7fdb433bb 100644 --- a/src/platform/qt/VideoDumper.cpp +++ b/src/platform/qt/VideoDumper.cpp @@ -43,9 +43,11 @@ bool VideoDumper::present(const QVideoFrame& frame) { case QVideoFrame::Format_YUV420P: pixelFormat = AV_PIX_FMT_YUV420P; break; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) case QVideoFrame::Format_YUV422P: pixelFormat = AV_PIX_FMT_YUV422P; break; +#endif case QVideoFrame::Format_YUYV: pixelFormat = AV_PIX_FMT_YUYV422; break; @@ -129,7 +131,9 @@ QList VideoDumper::supportedPixelFormats(QAbstractVide #ifdef USE_FFMPEG list.append(QVideoFrame::Format_YUYV); list.append(QVideoFrame::Format_UYVY); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) list.append(QVideoFrame::Format_YUV422P); +#endif list.append(QVideoFrame::Format_YUV420P); list.append(QVideoFrame::Format_NV12); list.append(QVideoFrame::Format_NV21); From 83f0deab1b47b693c06fd0d937e6b559fc2dbbd2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 10 Sep 2022 04:01:38 -0700 Subject: [PATCH 07/37] Scripting: Fix some memory leaks --- src/script/types.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/script/types.c b/src/script/types.c index 71d2b7e07..8ce4df461 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -266,10 +266,11 @@ void _allocList(struct mScriptValue* val) { void _freeList(struct mScriptValue* val) { size_t i; for (i = 0; i < mScriptListSize(val->value.list); ++i) { - if (val->type) { + struct mScriptValue* value = mScriptListGetPointer(val->value.list, i); + if (!value->type) { continue; } - struct mScriptValue* unwrapped = mScriptValueUnwrap(mScriptListGetPointer(val->value.list, i)); + struct mScriptValue* unwrapped = mScriptValueUnwrap(value); if (unwrapped) { mScriptValueDeref(unwrapped); } @@ -308,7 +309,7 @@ static void _allocString(struct mScriptValue* val) { static void _freeString(struct mScriptValue* val) { struct mScriptString* string = val->value.string; - if (string->size) { + if (string->buffer) { free(string->buffer); } free(string); From 5dbe2404423e3c24703694cfe3393bebddfb4323 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 11 Sep 2022 02:49:04 -0700 Subject: [PATCH 08/37] Qt: Resume crashed game when loading a save state --- CHANGES | 1 + include/mgba/core/thread.h | 6 ++++-- src/core/thread.c | 13 +++++++++++++ src/platform/qt/CoreController.cpp | 3 +++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 8e02fa02e..35a4bf926 100644 --- a/CHANGES +++ b/CHANGES @@ -107,6 +107,7 @@ Misc: - Qt: Improve cheat parsing (fixes mgba.io/i/2297) - Qt: Change lossless setting to use WavPack audio - Qt: Use FFmpeg to convert additional camera formats, if available + - Qt: Resume crashed game when loading a save state - SDL: Support exposing an axis directly as the gyro value (closes mgba.io/i/2531) - Windows: Attach to console if present - Vita: Add bilinear filtering option (closes mgba.io/i/344) diff --git a/include/mgba/core/thread.h b/include/mgba/core/thread.h index 55d6a99e8..fee447ae7 100644 --- a/include/mgba/core/thread.h +++ b/include/mgba/core/thread.h @@ -101,8 +101,6 @@ struct mCoreThreadInternal { bool mCoreThreadStart(struct mCoreThread* threadContext); bool mCoreThreadHasStarted(struct mCoreThread* threadContext); bool mCoreThreadHasExited(struct mCoreThread* threadContext); -bool mCoreThreadHasCrashed(struct mCoreThread* threadContext); -void mCoreThreadMarkCrashed(struct mCoreThread* threadContext); void mCoreThreadEnd(struct mCoreThread* threadContext); void mCoreThreadReset(struct mCoreThread* threadContext); void mCoreThreadJoin(struct mCoreThread* threadContext); @@ -122,6 +120,10 @@ void mCoreThreadPauseFromThread(struct mCoreThread* threadContext); void mCoreThreadWaitFromThread(struct mCoreThread* threadContext); void mCoreThreadStopWaiting(struct mCoreThread* threadContext); +bool mCoreThreadHasCrashed(struct mCoreThread* threadContext); +void mCoreThreadMarkCrashed(struct mCoreThread* threadContext); +void mCoreThreadClearCrashed(struct mCoreThread* threadContext); + void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool); void mCoreThreadRewindParamsChanged(struct mCoreThread* threadContext); diff --git a/src/core/thread.c b/src/core/thread.c index ad65ae51d..7101b5a54 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -526,6 +526,15 @@ void mCoreThreadMarkCrashed(struct mCoreThread* threadContext) { MutexUnlock(&threadContext->impl->stateMutex); } +void mCoreThreadClearCrashed(struct mCoreThread* threadContext) { + MutexLock(&threadContext->impl->stateMutex); + if (threadContext->impl->state == mTHREAD_CRASHED) { + threadContext->impl->state = mTHREAD_REQUEST; + ConditionWake(&threadContext->impl->stateCond); + } + MutexUnlock(&threadContext->impl->stateMutex); +} + void mCoreThreadEnd(struct mCoreThread* threadContext) { MutexLock(&threadContext->impl->stateMutex); _waitOnInterrupt(threadContext->impl); @@ -685,6 +694,10 @@ void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) { void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) { MutexLock(&threadContext->impl->stateMutex); threadContext->impl->rewinding = rewinding; + if (rewinding && threadContext->impl->state == mTHREAD_CRASHED) { + threadContext->impl->state = mTHREAD_REQUEST; + ConditionWake(&threadContext->impl->stateCond); + } MutexUnlock(&threadContext->impl->stateMutex); } diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 421433191..61774de71 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -613,6 +613,7 @@ void CoreController::loadState(int slot) { m_stateSlot = slot; m_backupSaveState.clear(); } + mCoreThreadClearCrashed(&m_threadContext); mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); if (!controller->m_backupLoadState.isOpen()) { @@ -632,6 +633,7 @@ void CoreController::loadState(const QString& path, int flags) { if (flags != -1) { m_loadStateFlags = flags; } + mCoreThreadClearCrashed(&m_threadContext); mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY); @@ -660,6 +662,7 @@ void CoreController::loadState(QIODevice* iodev, int flags) { if (flags != -1) { m_loadStateFlags = flags; } + mCoreThreadClearCrashed(&m_threadContext); mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); VFile* vf = controller->m_stateVf; From 084b56b3bfc66eda7576bfdee26a18785afa6707 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 12 Sep 2022 00:13:19 -0700 Subject: [PATCH 09/37] GB: Remove faulty debug logging --- src/gb/gb.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/gb/gb.c b/src/gb/gb.c index 2f1fe3ba9..bb07f9d1b 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -854,13 +854,6 @@ static void _GBAdvanceCycles(struct GB* gb) { void GBProcessEvents(struct SM83Core* cpu) { struct GB* gb = (struct GB*) cpu->master; -#ifndef NDEBUG - int stateMask = (4 * (2 - gb->doubleSpeed)) - 1; - int state = (mTimingGlobalTime(&gb->timing) & stateMask) >> !gb->doubleSpeed; - if (((state + 3) & 3) != (cpu->executionState & 3)) { - mLOG(GB, ERROR, "T-states and M-cycles became misaligned"); - } -#endif bool wasHalted = cpu->halted; while (true) { do { From a11b103a9c3eff5a1ff42311e6c7b34def92eef7 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Mon, 12 Sep 2022 11:35:55 -0500 Subject: [PATCH 10/37] Util: Add DNS resolution and SO_REUSEADDR to sockets --- include/mgba-util/socket.h | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/include/mgba-util/socket.h b/include/mgba-util/socket.h index bf3a9e3ad..731970d54 100644 --- a/include/mgba-util/socket.h +++ b/include/mgba-util/socket.h @@ -21,11 +21,14 @@ CXX_GUARD_START typedef SOCKET Socket; #else #ifdef GEKKO +#define USE_GETHOSTBYNAME #include #else #include +#include #include #include +#include #include #endif #include @@ -191,6 +194,17 @@ static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress) } int err; + + int enable = 1; +#ifdef GEKKO + err = net_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); +#else + err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); +#endif + if (err) { + return INVALID_SOCKET; + } + if (!bindAddress) { struct sockaddr_in bindInfo; memset(&bindInfo, 0, sizeof(bindInfo)); @@ -438,6 +452,62 @@ static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Soc return result; } +static inline int SocketResolveHost(const char* addrString, struct Address* destAddress) { + int result = 0; +#ifdef USE_GETHOSTBYNAME +#warning Using gethostbyname() for hostname resolution is not threadsafe +#ifdef GEKKO + struct hostent* host = net_gethostbyname(addrString); +#else + struct hostent* host = gethostbyname(addrString); +#endif + if (!host) { + return errno; + } + family = host->h_addrtype; + if (host->h_addrtype == AF_INET && host->h_length == 4) { + destAddress->version = IPV4; + destAddress->ipv4 = ntohl(*host->h_addr_list[0]); + } else if (host->h_addrtype == AF_INET6 && host->h_length == 16) { + destAddress->version = IPV6; + memcpy(destAddress->ipv6, host->h_addr_list[0], 16); + } else { + result = NO_DATA; + } +#else + struct addrinfo* addr = NULL; + result = getaddrinfo(addrString, NULL, NULL, &addr); + if (result) { +#ifndef _WIN32 + if (result == EAI_SYSTEM) { + result = errno; + } +#endif + goto error; + } + if (addr->ai_family == AF_INET && addr->ai_addrlen == sizeof(struct sockaddr_in)) { + struct sockaddr_in* addr4 = (struct sockaddr_in*) addr->ai_addr; + destAddress->version = IPV4; + destAddress->ipv4 = ntohl(addr4->sin_addr.s_addr); + } else if (addr->ai_family == AF_INET6 && addr->ai_addrlen == sizeof(struct sockaddr_in6)) { + struct sockaddr_in6* addr6 = (struct sockaddr_in6*) addr->ai_addr; + destAddress->version = IPV6; + memcpy(destAddress->ipv6, addr6->sin6_addr.s6_addr, 16); + } else { +#ifdef _WIN32 + result = WSANO_DATA; +#else + result = EAI_NODATA; +#endif + } +error: + if (addr) { + freeaddrinfo(addr); + } +#endif + return result; +} + CXX_GUARD_END #endif From d852c7c8f0bce4e4332d66c99da654fe6d2218f6 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Mon, 12 Sep 2022 11:50:54 -0500 Subject: [PATCH 11/37] Scripting: add socket bindings --- include/mgba/core/scripting.h | 3 - include/mgba/internal/script/socket.h | 25 ++ include/mgba/script/context.h | 4 + .../qt/scripting/ScriptingController.cpp | 1 + src/script/CMakeLists.txt | 1 + src/script/engines/lua.c | 172 +++++++++++ src/script/socket.c | 276 ++++++++++++++++++ 7 files changed, 479 insertions(+), 3 deletions(-) create mode 100644 include/mgba/internal/script/socket.h create mode 100644 src/script/socket.c diff --git a/include/mgba/core/scripting.h b/include/mgba/core/scripting.h index 6c0943e46..b63102ff8 100644 --- a/include/mgba/core/scripting.h +++ b/include/mgba/core/scripting.h @@ -10,15 +10,12 @@ CXX_GUARD_START -#include #ifdef USE_DEBUGGERS #include #endif #include #include -mLOG_DECLARE_CATEGORY(SCRIPT); - struct mCore; struct mScriptTextBuffer; mSCRIPT_DECLARE_STRUCT(mCore); diff --git a/include/mgba/internal/script/socket.h b/include/mgba/internal/script/socket.h new file mode 100644 index 000000000..014a765bb --- /dev/null +++ b/include/mgba/internal/script/socket.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2013-2022 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/. */ +#ifndef M_SCRIPT_SOCKET_H +#define M_SCRIPT_SOCKET_H + +enum mSocketErrorCode { + mSCRIPT_SOCKERR_UNKNOWN_ERROR = -1, + mSCRIPT_SOCKERR_OK = 0, + mSCRIPT_SOCKERR_AGAIN, + mSCRIPT_SOCKERR_ADDRESS_IN_USE, + mSCRIPT_SOCKERR_CONNECTION_REFUSED, + mSCRIPT_SOCKERR_DENIED, + mSCRIPT_SOCKERR_FAILED, + mSCRIPT_SOCKERR_NETWORK_UNREACHABLE, + mSCRIPT_SOCKERR_NOT_FOUND, + mSCRIPT_SOCKERR_NO_DATA, + mSCRIPT_SOCKERR_OUT_OF_MEMORY, + mSCRIPT_SOCKERR_TIMEOUT, + mSCRIPT_SOCKERR_UNSUPPORTED, +}; + +#endif diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index 753c356ba..ff4b34223 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -10,6 +10,7 @@ CXX_GUARD_START +#include #include #include #include @@ -18,6 +19,8 @@ CXX_GUARD_START #define mSCRIPT_CONSTANT_PAIR(NS, CONST) { #CONST, mScriptValueCreateFromSInt(NS ## _ ## CONST) } #define mSCRIPT_KV_SENTINEL { NULL, NULL } +mLOG_DECLARE_CATEGORY(SCRIPT); + struct mScriptFrame; struct mScriptFunction; struct mScriptEngineContext; @@ -83,6 +86,7 @@ struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext*, struct void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref); void mScriptContextAttachStdlib(struct mScriptContext* context); +void mScriptContextAttachSocket(struct mScriptContext* context); void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants); void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value); diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index a9a108e14..cd1e716d4 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -113,6 +113,7 @@ void ScriptingController::runCode(const QString& code) { void ScriptingController::init() { mScriptContextInit(&m_scriptContext); mScriptContextAttachStdlib(&m_scriptContext); + mScriptContextAttachSocket(&m_scriptContext); mScriptContextRegisterEngines(&m_scriptContext); mScriptContextAttachLogger(&m_scriptContext, &m_logger); diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index e9ecc8c60..5cfb4ff41 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -1,6 +1,7 @@ include(ExportDirectory) set(SOURCE_FILES context.c + socket.c stdlib.c types.c) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index caca84d03..3d9886d81 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include +#include #include #include @@ -51,6 +53,156 @@ static int _luaLenList(lua_State* lua); static int _luaRequireShim(lua_State* lua); +static const char* _socketLuaSource = + "socket = {\n" + " ERRORS = {},\n" + " tcp = function() return socket._create(_socket.create(), socket._tcpMT) end,\n" + " bind = function(address, port)\n" + " local s = socket.tcp()\n" + " local ok, err = s:bind(address, port)\n" + " if ok then return s end\n" + " return ok, err\n" + " end,\n" + " connect = function(address, port)\n" + " local s = socket.tcp()\n" + " local ok, err = s:connect(address, port)\n" + " if ok then return s end\n" + " return ok, err\n" + " end,\n" + " _create = function(sock, mt) return setmetatable({\n" + " _s = sock,\n" + " _callbacks = {},\n" + " _nextCallback = 1,\n" + " }, mt) end,\n" + " _wrap = function(status)\n" + " if status == 0 then return 1 end\n" + " return nil, socket.ERRORS[status] or ('error#' .. status)\n" + " end,\n" + " _mt = {\n" + " __index = {\n" + " close = function(self)\n" + " if self._onframecb then\n" + " callbacks:remove(self._onframecb)\n" + " self._onframecb = nil\n" + " end\n" + " self._callbacks = {}\n" + " return self._s:close()\n" + " end,\n" + " add = function(self, event, callback)\n" + " if not self._callbacks[event] then self._callbacks[event] = {} end\n" + " local cbid = self._nextCallback\n" + " self._nextCallback = cbid + 1\n" + " self._callbacks[event][cbid] = callback\n" + " return id\n" + " end,\n" + " remove = function(self, cbid)\n" + " for _, group in pairs(self._callbacks) do\n" + " if group[cbid] then\n" + " group[cbid] = nil\n" + " end\n" + " end\n" + " end,\n" + " _dispatch = function(self, event, ...)\n" + " if not self._callbacks[event] then return end\n" + " for k, cb in pairs(self._callbacks[event]) do\n" + " if cb then\n" + " local ok, ret = pcall(cb, self, ...)\n" + " if not ok then console:error(ret) end\n" + " end\n" + " end\n" + " end,\n" + " },\n" + " },\n" + " _tcpMT = {\n" + " __index = {\n" + " _hook = function(self, status)\n" + " if status == 0 then\n" + " self._onframecb = callbacks:add('frame', function() self:poll() end)\n" + " end\n" + " return socket._wrap(status)\n" + " end,\n" + " bind = function(self, address, port)\n" + " return socket._wrap(self._s:open(address or '', port))\n" + " end,\n" + " connect = function(self, address, port)\n" + " local status = self._s:connect(address, port)\n" + " end,\n" + " listen = function(self, backlog)\n" + " local status = self._s:listen(backlog or 1)\n" + " return self:_hook(status)\n" + " end,\n" + " accept = function(self)\n" + " local client = self._s:accept()\n" + " if client.error ~= 0 then\n" + " client:close()\n" + " return socket._wrap(client.error)\n" + " end\n" + " local sock = socket._create(client, socket._tcpMT)\n" + " sock:_hook(0)\n" + " return sock\n" + " end,\n" + " send = function(self, data, i, j)\n" + " local result = self._s:send(string.sub(data, i or 1, j))\n" + " if result < 0 then return socket._wrap(self._s.error) end\n" + " if i then return result + i - 1 end\n" + " return result\n" + " end,\n" + // TODO: This does not match the API for LuaSocket's receive() implementation + " receive = function(self, maxBytes)\n" + " local result = self._s:recv(maxBytes)\n" + " if (not result or #result == 0) and self._s.error ~= 0 then\n" + " return socket._wrap(self._s.error)\n" + " elseif not result or #result == 0 then\n" + " return nil, 'disconnected'\n" + " end\n" + " return result or ''\n" + " end,\n" + " hasdata = function(self)\n" + " local status = self._s:select(0)\n" + " if status < 0 then\n" + " return socket._wrap(self._s.error)\n" + " end\n" + " return status > 0\n" + " end,\n" + " poll = function(self)\n" + " local status, err = self:hasdata()\n" + " if err then\n" + " self:_dispatch('error', err)\n" + " elseif status then\n" + " self:_dispatch('received')\n" + " end\n" + " end,\n" + " },\n" + " },\n" + " _errMT = {\n" + " __index = function (tbl, key)\n" + " return rawget(tbl, C.SOCKERR[key])\n" + " end,\n" + " },\n" + "}\n" + "setmetatable(socket._tcpMT.__index, socket._mt)\n" + "setmetatable(socket.ERRORS, socket._errMT)\n"; + +static const struct _mScriptSocketError { + enum mSocketErrorCode err; + const char* message; +} _mScriptSocketErrors[] = { + { mSCRIPT_SOCKERR_UNKNOWN_ERROR, "unknown error" }, + { mSCRIPT_SOCKERR_OK, NULL }, + { mSCRIPT_SOCKERR_AGAIN, "temporary failure" }, + { mSCRIPT_SOCKERR_ADDRESS_IN_USE, "address in use" }, + { mSCRIPT_SOCKERR_DENIED, "access denied" }, + { mSCRIPT_SOCKERR_UNSUPPORTED, "unsupported" }, + { mSCRIPT_SOCKERR_CONNECTION_REFUSED, "connection refused" }, + { mSCRIPT_SOCKERR_NETWORK_UNREACHABLE, "network unreachable" }, + { mSCRIPT_SOCKERR_TIMEOUT, "timeout" }, + { mSCRIPT_SOCKERR_FAILED, "failed" }, + { mSCRIPT_SOCKERR_NOT_FOUND, "not found" }, + { mSCRIPT_SOCKERR_NO_DATA, "no data" }, + { mSCRIPT_SOCKERR_OUT_OF_MEMORY, "out of memory" }, +}; +static const int _mScriptSocketNumErrors = sizeof(_mScriptSocketErrors) / sizeof(struct _mScriptSocketError); + #if LUA_VERSION_NUM < 503 #define lua_pushinteger lua_pushnumber #endif @@ -177,6 +329,26 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS lua_getglobal(luaContext->lua, "require"); luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); + int status = luaL_dostring(luaContext->lua, _socketLuaSource); + if (status) { + mLOG(SCRIPT, ERROR, "Error in dostring while initializing sockets: %s\n", lua_tostring(luaContext->lua, -1)); + lua_pop(luaContext->lua, 1); + } else { + int i; + lua_getglobal(luaContext->lua, "socket"); + lua_getfield(luaContext->lua, -1, "ERRORS"); + for (i = 0; i < _mScriptSocketNumErrors; i++) { + struct _mScriptSocketError* err = &_mScriptSocketErrors[i]; + if (err->message) { + lua_pushstring(luaContext->lua, err->message); + } else { + lua_pushnil(luaContext->lua); + } + lua_seti(luaContext->lua, -2, err->err); + } + lua_pop(luaContext->lua, 2); + } + return &luaContext->d; } diff --git a/src/script/socket.c b/src/script/socket.c new file mode 100644 index 000000000..bdf66992a --- /dev/null +++ b/src/script/socket.c @@ -0,0 +1,276 @@ +/* Copyright (c) 2013-2022 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 + +#include +#include +#include + +struct mScriptSocket { + Socket socket; + struct Address address; + int32_t error; + uint16_t port; +}; +mSCRIPT_DECLARE_STRUCT(mScriptSocket); + +static const struct _mScriptSocketErrorMapping { + int32_t nativeError; + enum mSocketErrorCode mappedError; +} _mScriptSocketErrorMappings[] = { + { EAGAIN, mSCRIPT_SOCKERR_AGAIN }, + { EADDRINUSE, mSCRIPT_SOCKERR_ADDRESS_IN_USE }, + { ECONNREFUSED, mSCRIPT_SOCKERR_CONNECTION_REFUSED }, + { EACCES, mSCRIPT_SOCKERR_DENIED }, + { EPERM, mSCRIPT_SOCKERR_DENIED }, + { ENOTRECOVERABLE, mSCRIPT_SOCKERR_FAILED }, + { ENETUNREACH, mSCRIPT_SOCKERR_NETWORK_UNREACHABLE }, + { ETIMEDOUT, mSCRIPT_SOCKERR_TIMEOUT }, + { EINVAL, mSCRIPT_SOCKERR_UNSUPPORTED }, + { EPROTONOSUPPORT, mSCRIPT_SOCKERR_UNSUPPORTED }, +#ifndef USE_GETHOSTBYNAME +#ifdef _WIN32 + { WSATRY_AGAIN, mSCRIPT_SOCKERR_AGAIN }, + { WSANO_RECOVERY, mSCRIPT_SOCKERR_FAILED }, + { WSANO_DATA, mSCRIPT_SOCKERR_NO_DATA }, + { WSAHOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND }, + { WSATYPE_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND }, + { WSA_NOT_ENOUGH_MEMORY, mSCRIPT_SOCKERR_OUT_OF_MEMORY }, + { WSAEAFNOSUPPORT, mSCRIPT_SOCKERR_UNSUPPORTED }, + { WSAEINVAL, mSCRIPT_SOCKERR_UNSUPPORTED }, + { WSAESOCKTNOSUPPORT, mSCRIPT_SOCKERR_UNSUPPORTED }, +#else + { EAI_AGAIN, mSCRIPT_SOCKERR_AGAIN }, + { EAI_FAIL, mSCRIPT_SOCKERR_FAILED }, + { EAI_NODATA, mSCRIPT_SOCKERR_NO_DATA }, + { EAI_NONAME, mSCRIPT_SOCKERR_NOT_FOUND }, + { EAI_MEMORY, mSCRIPT_SOCKERR_OUT_OF_MEMORY }, +#endif +#else + { -TRY_AGAIN, mSCRIPT_SOCKERR_AGAIN }, + { -NO_RECOVERY, mSCRIPT_SOCKERR_FAILED }, + { -NO_DATA, mSCRIPT_SOCKERR_NO_DATA }, + { -HOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND }, +#endif +}; +static const int _mScriptSocketNumErrorMappings = sizeof(_mScriptSocketErrorMappings) / sizeof(struct _mScriptSocketErrorMapping); + +static void _mScriptSocketSetError(struct mScriptSocket* ssock, int32_t err) { + if (!err) { + ssock->error = mSCRIPT_SOCKERR_OK; + return; + } + int i; + for (i = 0; i < _mScriptSocketNumErrorMappings; i++) { + if (_mScriptSocketErrorMappings[i].nativeError == err) { + ssock->error = _mScriptSocketErrorMappings[i].mappedError; + return; + } + } + ssock->error = mSCRIPT_SOCKERR_UNKNOWN_ERROR; +} + +static void _mScriptSocketSetSocket(struct mScriptSocket* ssock, Socket socket) { + if (SOCKET_FAILED(socket)) { + ssock->socket = INVALID_SOCKET; + _mScriptSocketSetError(ssock, SocketError()); + } else { + ssock->socket = socket; + ssock->error = mSCRIPT_SOCKERR_OK; + } +} + +struct mScriptValue* _mScriptSocketCreate() { + struct mScriptSocket client = { + .socket = INVALID_SOCKET, + .error = mSCRIPT_SOCKERR_OK, + .port = 0 + }; + + struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptSocket)); + result->value.opaque = calloc(1, sizeof(struct mScriptSocket)); + *(struct mScriptSocket*) result->value.opaque = client; + result->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; + return result; +} + +void _mScriptSocketClose(struct mScriptSocket* ssock) { + if (!SOCKET_FAILED(ssock->socket)) { + SocketClose(ssock->socket); + } +} + +struct mScriptValue* _mScriptSocketAccept(struct mScriptSocket* ssock) { + struct mScriptValue* value = _mScriptSocketCreate(); + struct mScriptSocket* client = (struct mScriptSocket*) value->value.opaque; + _mScriptSocketSetSocket(client, SocketAccept(ssock->socket, &client->address)); + if (!client->error) { + SocketSetBlocking(client->socket, false); + } + return value; +} + +int32_t _mScriptSocketOpen(struct mScriptSocket* ssock, const char* addressStr, uint16_t port) { + struct Address* addr = NULL; + if (addressStr && addressStr[0]) { + int32_t err = SocketResolveHost(addressStr, &ssock->address); + if (err) { + _mScriptSocketSetError(ssock, err); + return err; + } + addr = &ssock->address; + } + ssock->port = port; + _mScriptSocketSetSocket(ssock, SocketOpenTCP(port, addr)); + if (!ssock->error) { + SocketSetBlocking(ssock->socket, false); + } + return ssock->error; +} + +int32_t _mScriptSocketConnect(struct mScriptSocket* ssock, const char* addressStr, uint16_t port) { + int32_t err = SocketResolveHost(addressStr, &ssock->address); + if (err) { + _mScriptSocketSetError(ssock, err); + return err; + } + ssock->port = port; + _mScriptSocketSetSocket(ssock, SocketConnectTCP(port, &ssock->address)); + if (!ssock->error) { + SocketSetBlocking(ssock->socket, false); + } + return ssock->error; +} + +int32_t _mScriptSocketListen(struct mScriptSocket* ssock, uint32_t queueLength) { + _mScriptSocketSetError(ssock, SocketListen(ssock->socket, queueLength)); + return ssock->error; +} + +int32_t _mScriptSocketSend(struct mScriptSocket* ssock, struct mScriptString* data) { + ssize_t written = SocketSend(ssock->socket, data->buffer, data->size); + if (written < 0) { + _mScriptSocketSetError(ssock, SocketError()); + return -ssock->error; + } + ssock->error = mSCRIPT_SOCKERR_OK; + return written; +} + +struct mScriptValue* _mScriptSocketRecv(struct mScriptSocket* ssock, uint32_t maxBytes) { + struct mScriptValue* value = mScriptStringCreateEmpty(maxBytes); + struct mScriptString* data = value->value.string; + ssize_t bytes = SocketRecv(ssock->socket, data->buffer, maxBytes); + if (bytes <= 0) { + data->size = 0; + _mScriptSocketSetError(ssock, SocketError()); + } else { + data->size = bytes; + ssock->error = mSCRIPT_SOCKERR_OK; + } + return value; +} + +// This works sufficiently well for a single socket, but it could be better. +// Ideally, all sockets would be tracked and selected on together for efficiency. +uint32_t _mScriptSocketSelectOne(struct mScriptSocket* ssock, int64_t timeoutMillis) { + Socket reads[] = { ssock->socket }; + Socket errors[] = { ssock->socket }; + int result = SocketPoll(1, reads, NULL, errors, timeoutMillis); + if (!result) { + return 0; + } else if (errors[0] != INVALID_SOCKET) { + _mScriptSocketSetError(ssock, SocketError()); + return -1; + } + return 1; +} + +mSCRIPT_BIND_FUNCTION(mScriptSocketCreate_Binding, W(mScriptSocket), _mScriptSocketCreate, 0); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptSocket, close, _mScriptSocketClose, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, W(mScriptSocket), accept, _mScriptSocketAccept, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, open, _mScriptSocketOpen, 2, CHARP, address, U16, port); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, connect, _mScriptSocketConnect, 2, CHARP, address, U16, port); +mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptSocket, S32, listen, _mScriptSocketListen, 1, U32, queueLength); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, send, _mScriptSocketSend, 1, STR, data); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, WSTR, recv, _mScriptSocketRecv, 1, U32, maxBytes); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, select, _mScriptSocketSelectOne, 1, S64, timeoutMillis); + +mSCRIPT_DEFINE_STRUCT(mScriptSocket) + mSCRIPT_DEFINE_CLASS_DOCSTRING("An internal implementation of a TCP network socket.") + mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(mScriptSocket, close) + mSCRIPT_DEFINE_DOCSTRING("Closes the socket. If the socket is already closed, this function does nothing.") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, close) + mSCRIPT_DEFINE_DOCSTRING("Creates a new socket for an incoming connection from a listening server socket.") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, accept) + mSCRIPT_DEFINE_DOCSTRING( + "Binds the socket to a specified address and port. " + "If no address is specified, the socket is bound to all network interfaces." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, open) + mSCRIPT_DEFINE_DOCSTRING( + "Opens a TCP connection to the specified address and port.\n" + "**Caution:** This is a blocking call. The emulator will not respond until " + "the connection either succeeds or fails." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, connect) + mSCRIPT_DEFINE_DOCSTRING( + "Begins listening for incoming connections. The socket must have first been " + "bound with the `open` function." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, listen) + mSCRIPT_DEFINE_DOCSTRING( + "Sends data over a socket. Returns the number of bytes written, or -1 if an " + "error occurs." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, send) + mSCRIPT_DEFINE_DOCSTRING( + "Reads data from a socket, up to the specified number of bytes. " + "If the socket has been disconnected, this function returns an empty string. " + "Use `select` to test if data is available to be read." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, recv) + mSCRIPT_DEFINE_DOCSTRING( + "Checks the status of the socket. " + "Returns 1 if data is available to be read. " + "Returns -1 if an error has occurred on the socket." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, select) + mSCRIPT_DEFINE_DOCSTRING( + "One of the `SOCKERR` constants describing the last error on the socket." + ) + mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptSocket, S32, error) +mSCRIPT_DEFINE_END; + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptSocket, listen) + mSCRIPT_S32(1) +mSCRIPT_DEFINE_DEFAULTS_END; + + +void mScriptContextAttachSocket(struct mScriptContext* context) { + mScriptContextExportNamespace(context, "_socket", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(create, &mScriptSocketCreate_Binding), + mSCRIPT_KV_SENTINEL + }); + mScriptContextSetDocstring(context, "_socket", "Basic TCP sockets library"); + mScriptContextSetDocstring(context, "_socket.create", "Creates a new socket object"); + mScriptContextExportConstants(context, "SOCKERR", (struct mScriptKVPair[]) { + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, UNKNOWN_ERROR), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, OK), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, AGAIN), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, ADDRESS_IN_USE), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, CONNECTION_REFUSED), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, DENIED), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, FAILED), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, NETWORK_UNREACHABLE), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, NOT_FOUND), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, NO_DATA), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, OUT_OF_MEMORY), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, TIMEOUT), + mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, UNSUPPORTED), + mSCRIPT_KV_SENTINEL + }); +} From 6822e04c08003efe59a46d30e5e0cd2259c1aa3e Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Mon, 12 Sep 2022 11:51:21 -0500 Subject: [PATCH 12/37] Res: sample socket client and socket server scripts --- res/scripts/socketserver.lua | 103 +++++++++++++++++++++++++++++++++++ res/scripts/sockettest.lua | 69 +++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 res/scripts/socketserver.lua create mode 100644 res/scripts/sockettest.lua diff --git a/res/scripts/socketserver.lua b/res/scripts/socketserver.lua new file mode 100644 index 000000000..518887fa6 --- /dev/null +++ b/res/scripts/socketserver.lua @@ -0,0 +1,103 @@ +lastkeys = nil +server = nil +ST_sockets = {} +nextID = 1 + +local KEY_NAMES = { "A", "B", "s", "S", "<", ">", "^", "v", "R", "L" } + +function ST_stop(id) + local sock = ST_sockets[id] + ST_sockets[id] = nil + sock:close() +end + +function ST_format(id, msg, isError) + local prefix = "Socket " .. id + if isError then + prefix = prefix .. " Error: " + else + prefix = prefix .. " Received: " + end + return prefix .. msg +end + +function ST_error(id, err) + console:error(ST_format(id, err, true)) + ST_stop(id) +end + +function ST_received(id) + local sock = ST_sockets[id] + if not sock then return end + while true do + local p, err = sock:receive(1024) + if p then + console:log(ST_format(id, p:match("^(.-)%s*$"))) + else + if err ~= socket.ERRORS.AGAIN then + console:error(ST_format(id, err, true)) + ST_stop(id) + end + return + end + end +end + +function ST_scankeys() + local keys = emu:getKeys() + if keys ~= lastkeys then + lastkeys = keys + local msg = "[" + for i, k in ipairs(KEY_NAMES) do + if (keys & (1 << (i - 1))) == 0 then + msg = msg .. " " + else + msg = msg .. k; + end + end + msg = msg .. "]\n" + for id, sock in pairs(ST_sockets) do + if sock then sock:send(msg) end + end + end +end + +function ST_accept() + local sock, err = server:accept() + if err then + console:error(ST_format("Accept", err, true)) + return + end + local id = nextID + nextID = id + 1 + ST_sockets[id] = sock + sock:add("received", function() ST_received(id) end) + sock:add("error", function() ST_error(id) end) + console:log(ST_format(id, "Connected")) +end + +callbacks:add("keysRead", ST_scankeys) + +local port = 8888 +server = nil +while not server do + server, err = socket.bind(nil, port) + if err then + if err == "address in use" then + port = port + 1 + else + console:error(ST_format("Bind", err, true)) + break + end + else + local ok + ok, err = server:listen() + if err then + server:close() + console:error(ST_format("Listen", err, true)) + else + console:log("Socket Server Test: Listening on port " .. port) + server:add("received", ST_accept) + end + end +end diff --git a/res/scripts/sockettest.lua b/res/scripts/sockettest.lua new file mode 100644 index 000000000..c5a674576 --- /dev/null +++ b/res/scripts/sockettest.lua @@ -0,0 +1,69 @@ +sockettest = nil +lastkeys = nil + +local KEY_NAMES = { "A", "B", "s", "S", "<", ">", "^", "v", "R", "L" } + +function ST_stop() + if not sockettest then return end + console:log("Socket Test: Shutting down") + sockettest:close() + sockettest = nil +end + +function ST_start() + ST_stop() + console:log("Socket Test: Connecting to 127.0.0.1:8888...") + sockettest = socket.tcp() + sockettest:add("received", ST_received) + sockettest:add("error", ST_error) + if sockettest:connect("127.0.0.1", 8888) then + console:log("Socket Test: Connected") + lastkeys = nil + else + console:log("Socket Test: Failed to connect") + ST_stop() + end +end + +function ST_error(err) + console:error("Socket Test Error: " .. err) + ST_stop() +end + +function ST_received() + while true do + local p, err = sockettest:receive(1024) + if p then + console:log("Socket Test Received: " .. p:match("^(.-)%s*$")) + else + if err ~= socket.ERRORS.AGAIN then + console:error("Socket Test Error: " .. err) + ST_stop() + end + return + end + end +end + +function ST_scankeys() + if not sockettest then return end + local keys = emu:getKeys() + if keys ~= lastkeys then + lastkeys = keys + local msg = "[" + for i, k in ipairs(KEY_NAMES) do + if (keys & (1 << (i - 1))) == 0 then + msg = msg .. " " + else + msg = msg .. k; + end + end + sockettest:send(msg .. "]\n") + end +end + +callbacks:add("start", ST_start) +callbacks:add("stop", ST_stop) +callbacks:add("crashed", ST_stop) +callbacks:add("reset", ST_start) +callbacks:add("keysRead", ST_scankeys) From 2912bd2d078ed2778dd54660b5190a05eb6e172e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 12 Sep 2022 18:12:11 -0700 Subject: [PATCH 13/37] GB: Support CGB0 boot ROM loading --- CHANGES | 1 + src/gb/gb.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 35a4bf926..442380f5d 100644 --- a/CHANGES +++ b/CHANGES @@ -78,6 +78,7 @@ Misc: - Debugger: GDB now works while the game is paused - Debugger: Add command to load external symbol file (fixes mgba.io/i/2480) - FFmpeg: Support dynamic audio sample rate + - GB: Support CGB0 boot ROM loading - GB Audio: Increase sample rate - GB MBC: Filter out MBC errors when cartridge is yanked (fixes mgba.io/i/2488) - GB MBC: Partially implement TAMA5 RTC diff --git a/src/gb/gb.c b/src/gb/gb.c index bb07f9d1b..3f9fd59a6 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -33,6 +33,7 @@ static const uint8_t _registeredTrademark[] = {0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA #define SGB_BIOS_CHECKSUM 0xEC8A83B9 #define SGB2_BIOS_CHECKSUM 0X53D0DD63 #define CGB_BIOS_CHECKSUM 0x41884E46 +#define CGB0_BIOS_CHECKSUM 0xE8EF5318 #define AGB_BIOS_CHECKSUM 0xFFD6B0F1 mLOG_DEFINE_CATEGORY(GB, "GB", "gb"); @@ -513,6 +514,7 @@ bool GBIsBIOS(struct VFile* vf) { case SGB_BIOS_CHECKSUM: case SGB2_BIOS_CHECKSUM: case CGB_BIOS_CHECKSUM: + case CGB0_BIOS_CHECKSUM: case AGB_BIOS_CHECKSUM: return true; default: From c57075a53932ce62b1cf703009685efa34155e50 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 21 Sep 2022 20:04:38 -0700 Subject: [PATCH 14/37] Scripting: Buildfix --- include/mgba/core/scripting.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mgba/core/scripting.h b/include/mgba/core/scripting.h index b63102ff8..645874883 100644 --- a/include/mgba/core/scripting.h +++ b/include/mgba/core/scripting.h @@ -78,6 +78,7 @@ struct mScriptContext; void mScriptContextAttachCore(struct mScriptContext*, struct mCore*); void mScriptContextDetachCore(struct mScriptContext*); +struct mLogger; void mScriptContextAttachLogger(struct mScriptContext*, struct mLogger*); void mScriptContextDetachLogger(struct mScriptContext*); From 536170c3317da2a53b2f04fffbcb60e1e68ea017 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 21 Sep 2022 20:22:14 -0700 Subject: [PATCH 15/37] Windows: Buildfixes --- include/mgba-util/socket.h | 4 +++- src/script/socket.c | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/mgba-util/socket.h b/include/mgba-util/socket.h index 731970d54..67f635ca1 100644 --- a/include/mgba-util/socket.h +++ b/include/mgba-util/socket.h @@ -195,9 +195,11 @@ static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress) int err; - int enable = 1; + const int enable = 1; #ifdef GEKKO err = net_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); +#elif defined(_WIN32) + err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*) &enable, sizeof(enable)); #else err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); #endif diff --git a/src/script/socket.c b/src/script/socket.c index bdf66992a..a585c8563 100644 --- a/src/script/socket.c +++ b/src/script/socket.c @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include + #include #include #include From d4b3bcde0faa13dc5f749c41877156007932a4f0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 21 Sep 2022 20:41:34 -0700 Subject: [PATCH 16/37] Util: More buildfixes --- include/mgba-util/socket.h | 27 +++++++++++++++++++-------- src/script/socket.c | 2 ++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/include/mgba-util/socket.h b/include/mgba-util/socket.h index 67f635ca1..9f07491bd 100644 --- a/include/mgba-util/socket.h +++ b/include/mgba-util/socket.h @@ -466,21 +466,28 @@ static inline int SocketResolveHost(const char* addrString, struct Address* dest if (!host) { return errno; } - family = host->h_addrtype; if (host->h_addrtype == AF_INET && host->h_length == 4) { destAddress->version = IPV4; destAddress->ipv4 = ntohl(*host->h_addr_list[0]); - } else if (host->h_addrtype == AF_INET6 && host->h_length == 16) { + } +#ifdef HAS_IPV6 + else if (host->h_addrtype == AF_INET6 && host->h_length == 16) { destAddress->version = IPV6; memcpy(destAddress->ipv6, host->h_addr_list[0], 16); - } else { - result = NO_DATA; + } +#endif + else { +#ifdef GEKKO + result = errno; +#else + result = -h_errno; +#endif } #else struct addrinfo* addr = NULL; result = getaddrinfo(addrString, NULL, NULL, &addr); if (result) { -#ifndef _WIN32 +#ifdef EAI_SYSTEM if (result == EAI_SYSTEM) { result = errno; } @@ -491,15 +498,19 @@ static inline int SocketResolveHost(const char* addrString, struct Address* dest struct sockaddr_in* addr4 = (struct sockaddr_in*) addr->ai_addr; destAddress->version = IPV4; destAddress->ipv4 = ntohl(addr4->sin_addr.s_addr); - } else if (addr->ai_family == AF_INET6 && addr->ai_addrlen == sizeof(struct sockaddr_in6)) { + } +#ifdef HAS_IPV6 + else if (addr->ai_family == AF_INET6 && addr->ai_addrlen == sizeof(struct sockaddr_in6)) { struct sockaddr_in6* addr6 = (struct sockaddr_in6*) addr->ai_addr; destAddress->version = IPV6; memcpy(destAddress->ipv6, addr6->sin6_addr.s6_addr, 16); - } else { + } +#endif + else { #ifdef _WIN32 result = WSANO_DATA; #else - result = EAI_NODATA; + result = EAI_NONAME; #endif } error: diff --git a/src/script/socket.c b/src/script/socket.c index a585c8563..588fa7006 100644 --- a/src/script/socket.c +++ b/src/script/socket.c @@ -47,7 +47,9 @@ static const struct _mScriptSocketErrorMapping { #else { EAI_AGAIN, mSCRIPT_SOCKERR_AGAIN }, { EAI_FAIL, mSCRIPT_SOCKERR_FAILED }, +#ifdef EAI_NODATA { EAI_NODATA, mSCRIPT_SOCKERR_NO_DATA }, +#endif { EAI_NONAME, mSCRIPT_SOCKERR_NOT_FOUND }, { EAI_MEMORY, mSCRIPT_SOCKERR_OUT_OF_MEMORY }, #endif From 7520d8fc06f42a0b33de96ca40df65f3a3116fe9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 21 Sep 2022 22:57:12 -0700 Subject: [PATCH 17/37] Scripting: MSVC build fixes --- include/mgba-util/macros.h | 11 +++++++++++ include/mgba/script/macros.h | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/mgba-util/macros.h b/include/mgba-util/macros.h index 1ab494709..f994def47 100644 --- a/include/mgba-util/macros.h +++ b/include/mgba-util/macros.h @@ -57,6 +57,17 @@ #define _mODD_8(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__)) #define _mODD_9(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__)) +#define _mIF0_0(...) __VA_ARGS__ +#define _mIF0_1(...) +#define _mIF0_2(...) +#define _mIF0_3(...) +#define _mIF0_4(...) +#define _mIF0_5(...) +#define _mIF0_6(...) +#define _mIF0_7(...) +#define _mIF0_8(...) +#define _mIF0_9(...) + #define _mSUCC_0 1 #define _mSUCC_1 2 #define _mSUCC_2 3 diff --git a/include/mgba/script/macros.h b/include/mgba/script/macros.h index ddafe20fb..762a5b704 100644 --- a/include/mgba/script/macros.h +++ b/include/mgba/script/macros.h @@ -393,8 +393,8 @@ CXX_GUARD_START .function = { \ .parameters = { \ .count = NPARAMS, \ - .entries = { _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \ - .names = { _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \ + .entries = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \ + .names = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \ }, \ .returnType = { \ .count = NRET, \ @@ -433,7 +433,7 @@ CXX_GUARD_START _mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \ return true; \ } \ - _mSCRIPT_BIND_FUNCTION(NAME, 0, , NPARAMS, __VA_ARGS__) + _mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NPARAMS, __VA_ARGS__) #define mSCRIPT_MAKE(TYPE, VALUE) (struct mScriptValue) { \ .type = (mSCRIPT_TYPE_MS_ ## TYPE), \ From b60e0b9282c03261cd5b025ca7fb255f545b9c23 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Sun, 25 Sep 2022 18:57:03 -0500 Subject: [PATCH 18/37] Scripting: Return status from socket.connect, fix coding style --- res/scripts/socketserver.lua | 2 +- src/script/engines/lua.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/res/scripts/socketserver.lua b/res/scripts/socketserver.lua index 518887fa6..023a2d538 100644 --- a/res/scripts/socketserver.lua +++ b/res/scripts/socketserver.lua @@ -83,7 +83,7 @@ server = nil while not server do server, err = socket.bind(nil, port) if err then - if err == "address in use" then + if err == socket.ERRORS.ADDRESS_IN_USE then port = port + 1 else console:error(ST_format("Bind", err, true)) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 3d9886d81..353192d35 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -126,6 +126,7 @@ static const char* _socketLuaSource = " end,\n" " connect = function(self, address, port)\n" " local status = self._s:connect(address, port)\n" + " return socket._wrap(status)\n" " end,\n" " listen = function(self, backlog)\n" " local status = self._s:listen(backlog or 1)\n" From 68845e080ae21808c20246fdb3ca47fe56873c4a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 21 Sep 2022 23:30:38 -0700 Subject: [PATCH 19/37] Scripting: Const fix --- src/script/engines/lua.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 353192d35..a735e12be 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -339,7 +339,7 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS lua_getglobal(luaContext->lua, "socket"); lua_getfield(luaContext->lua, -1, "ERRORS"); for (i = 0; i < _mScriptSocketNumErrors; i++) { - struct _mScriptSocketError* err = &_mScriptSocketErrors[i]; + const struct _mScriptSocketError* err = &_mScriptSocketErrors[i]; if (err->message) { lua_pushstring(luaContext->lua, err->message); } else { From 95336463bf7e8cf1f10e1c0cd4a193f2b68331d0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 21 Sep 2022 23:52:25 -0700 Subject: [PATCH 20/37] Scripting: Add root scope function for engines --- include/mgba/script/context.h | 1 + src/script/engines/lua.c | 21 +++++++++++++ src/script/test/lua.c | 58 +++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index ff4b34223..10d4fa757 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -55,6 +55,7 @@ struct mScriptEngineContext { bool (*setGlobal)(struct mScriptEngineContext*, const char* name, struct mScriptValue*); struct mScriptValue* (*getGlobal)(struct mScriptEngineContext*, const char* name); + struct mScriptValue* (*rootScope)(struct mScriptEngineContext*); bool (*load)(struct mScriptEngineContext*, const char* filename, struct VFile*); bool (*run)(struct mScriptEngineContext*); diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index a735e12be..66a8b0eff 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -25,6 +25,7 @@ static void _luaDestroy(struct mScriptEngineContext*); static bool _luaIsScript(struct mScriptEngineContext*, const char*, struct VFile*); static struct mScriptValue* _luaGetGlobal(struct mScriptEngineContext*, const char* name); static bool _luaSetGlobal(struct mScriptEngineContext*, const char* name, struct mScriptValue*); +static struct mScriptValue* _luaRootScope(struct mScriptEngineContext*); static bool _luaLoad(struct mScriptEngineContext*, const char*, struct VFile*); static bool _luaRun(struct mScriptEngineContext*); static const char* _luaGetError(struct mScriptEngineContext*); @@ -214,6 +215,7 @@ static const int _mScriptSocketNumErrors = sizeof(_mScriptSocketErrors) / sizeof #if LUA_VERSION_NUM < 502 #define luaL_traceback(L, M, S, level) lua_pushstring(L, S) +#define lua_pushglobaltable(L) lua_pushvalue(L, LUA_GLOBALSINDEX) #endif const struct mScriptType mSTLuaFunc = { @@ -294,6 +296,7 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS .isScript = _luaIsScript, .getGlobal = _luaGetGlobal, .setGlobal = _luaSetGlobal, + .rootScope = _luaRootScope, .load = _luaLoad, .run = _luaRun, .getError = _luaGetError @@ -395,6 +398,24 @@ bool _luaSetGlobal(struct mScriptEngineContext* ctx, const char* name, struct mS return true; } +struct mScriptValue* _luaRootScope(struct mScriptEngineContext* ctx) { + struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) ctx; + + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + lua_pushglobaltable(luaContext->lua); + lua_pushnil(luaContext->lua); + while (lua_next(luaContext->lua, -2) != 0) { + struct mScriptValue* key; + + lua_pop(luaContext->lua, 1); + key = _luaCoerce(luaContext, false); + mScriptValueWrap(key, mScriptListAppend(list->value.list)); + } + lua_pop(luaContext->lua, 1); + + return list; +} + struct mScriptValue* _luaCoerceFunction(struct mScriptEngineContextLua* luaContext) { struct mScriptValue* value = mScriptValueAlloc(&mSTLuaFunc); struct mScriptFunction* fn = calloc(1, sizeof(*fn)); diff --git a/src/script/test/lua.c b/src/script/test/lua.c index 7790fed60..2cc7fcbff 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -259,6 +259,63 @@ M_TEST_DEFINE(setGlobal) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(rootScope) { + SETUP_LUA; + + struct mScriptValue* baseline = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + + struct mScriptValue* val; + val = lua->rootScope(lua); + assert_non_null(val); + assert_int_equal(val->type->base, mSCRIPT_TYPE_LIST); + + struct mScriptValue one = mSCRIPT_MAKE_S32(1); + size_t i; + for (i = 0; i < mScriptListSize(val->value.list); ++i) { + struct mScriptValue* key = mScriptListGetPointer(val->value.list, i); + if (key->type->base == mSCRIPT_TYPE_WRAPPER) { + key = mScriptValueUnwrap(key); + } + mScriptTableInsert(baseline, key, &one); + } + mScriptValueDeref(val); + + TEST_PROGRAM("foo = 1; bar = 2;"); + + bool fooFound = false; + bool barFound = false; + + val = lua->rootScope(lua); + assert_non_null(val); + assert_int_equal(val->type->base, mSCRIPT_TYPE_LIST); + assert_int_equal(mScriptListSize(val->value.list), mScriptTableSize(baseline) + 2); + + for (i = 0; i < mScriptListSize(val->value.list); ++i) { + struct mScriptValue* key = mScriptListGetPointer(val->value.list, i); + if (key->type->base == mSCRIPT_TYPE_WRAPPER) { + key = mScriptValueUnwrap(key); + } + if (mScriptTableLookup(baseline, key)) { + continue; + } + assert_int_equal(key->type->base, mSCRIPT_TYPE_STRING); + + if (strncmp(key->value.string->buffer, "foo", key->value.string->size) == 0) { + fooFound = true; + } + if (strncmp(key->value.string->buffer, "bar", key->value.string->size) == 0) { + barFound = true; + } + } + mScriptValueDeref(val); + + assert_true(fooFound); + assert_true(barFound); + + mScriptValueDeref(baseline); + mScriptContextDeinit(&context); +} + M_TEST_DEFINE(callLuaFunc) { SETUP_LUA; @@ -658,6 +715,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(runNop), cmocka_unit_test(getGlobal), cmocka_unit_test(setGlobal), + cmocka_unit_test(rootScope), cmocka_unit_test(callLuaFunc), cmocka_unit_test(callCFunc), cmocka_unit_test(globalStructFieldGet), From 73d19cc02ab775523dea3f1210c8262f95794e51 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 25 Sep 2022 17:21:26 -0700 Subject: [PATCH 21/37] Scripting: Add reference to the top-level engine in engine contexts --- include/mgba/script/context.h | 2 ++ src/script/engines/lua.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index 10d4fa757..7c8543cc5 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -49,6 +49,8 @@ struct mScriptEngine2 { struct mScriptEngineContext { struct mScriptContext* context; + struct mScriptEngine2* engine; + void (*destroy)(struct mScriptEngineContext*); bool (*isScript)(struct mScriptEngineContext*, const char* name, struct VFile* vf); diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 66a8b0eff..2a72172e9 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -288,10 +288,10 @@ static const luaL_Reg _mSTList[] = { }; struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mScriptContext* context) { - UNUSED(engine); struct mScriptEngineContextLua* luaContext = calloc(1, sizeof(*luaContext)); luaContext->d = (struct mScriptEngineContext) { .context = context, + .engine = engine, .destroy = _luaDestroy, .isScript = _luaIsScript, .getGlobal = _luaGetGlobal, From 8e898b02cc5ed4e1cd98f536fb63d1d515f269d8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 25 Sep 2022 17:29:50 -0700 Subject: [PATCH 22/37] Scripting: Add doc structs and exporting documentation from engines --- include/mgba/script/context.h | 6 ++ include/mgba/script/macros.h | 90 ++++++++++++++++++++++++++ include/mgba/script/types.h | 3 + src/script/context.c | 25 ++++++++ src/script/docgen.c | 117 +++++++++++++++++++++++++--------- src/script/engines/lua.c | 4 ++ 6 files changed, 215 insertions(+), 30 deletions(-) diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index 7c8543cc5..99eefe069 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -62,6 +62,8 @@ struct mScriptEngineContext { bool (*load)(struct mScriptEngineContext*, const char* filename, struct VFile*); bool (*run)(struct mScriptEngineContext*); const char* (*getError)(struct mScriptEngineContext*); + + struct Table docroot; }; struct mScriptKVPair { @@ -100,6 +102,10 @@ void mScriptContextRemoveCallback(struct mScriptContext*, uint32_t cbid); void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring); const char* mScriptContextGetDocstring(struct mScriptContext*, const char* key); +void mScriptEngineExportDocNamespace(struct mScriptEngineContext*, const char* nspace, struct mScriptKVPair* value); +void mScriptEngineSetDocstring(struct mScriptEngineContext*, const char* key, const char* docstring); +const char* mScriptEngineGetDocstring(struct mScriptEngineContext*, const char* key); + struct VFile; bool mScriptContextLoadVF(struct mScriptContext*, const char* name, struct VFile* vf); bool mScriptContextLoadFile(struct mScriptContext*, const char* path); diff --git a/include/mgba/script/macros.h b/include/mgba/script/macros.h index 762a5b704..34b59e0e3 100644 --- a/include/mgba/script/macros.h +++ b/include/mgba/script/macros.h @@ -182,6 +182,23 @@ CXX_GUARD_START .init = false, \ .details = (const struct mScriptClassInitDetails[]) { +#define mSCRIPT_DECLARE_DOC_STRUCT(SCOPE, STRUCT) \ + static const struct mScriptType mSTStruct_doc_ ## STRUCT; + +#define mSCRIPT_DEFINE_DOC_STRUCT(SCOPE, STRUCT) \ + static struct mScriptTypeClass _mSTStructDetails_doc_ ## STRUCT; \ + static const struct mScriptType mSTStruct_doc_ ## STRUCT = { \ + .base = mSCRIPT_TYPE_OBJECT, \ + .details = { \ + .cls = &_mSTStructDetails_doc_ ## STRUCT \ + }, \ + .size = 0, \ + .name = SCOPE "::struct::" #STRUCT, \ + }; \ + static struct mScriptTypeClass _mSTStructDetails_doc_ ## STRUCT = { \ + .init = false, \ + .details = (const struct mScriptClassInitDetails[]) { + #define mSCRIPT_DEFINE_DOCSTRING(DOCSTRING) { \ .type = mSCRIPT_CLASS_INIT_DOCSTRING, \ .info = { \ @@ -338,10 +355,48 @@ CXX_GUARD_START #define mSCRIPT_DECLARE_STRUCT_VOID_CD_METHOD_WITH_DEFAULTS(TYPE, NAME, NPARAMS, ...) \ mSCRIPT_DECLARE_STRUCT_VOID_C_METHOD_WITH_DEFAULTS(TYPE, NAME, p0->NAME, NPARAMS, __VA_ARGS__) +#define _mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, NRET, RETURN, NPARAMS, DEFAULTS, ...) \ + static const struct mScriptType _mSTStructBindingType_doc_ ## TYPE ## _ ## NAME = { \ + .base = mSCRIPT_TYPE_FUNCTION, \ + .name = SCOPE "::struct::" #TYPE "." #NAME, \ + .details = { \ + .function = { \ + .parameters = { \ + .count = _mSUCC_ ## NPARAMS, \ + .entries = { mSCRIPT_TYPE_MS_DS(TYPE), _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \ + .names = { "this", _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \ + .defaults = DEFAULTS, \ + }, \ + .returnType = { \ + .count = NRET, \ + .entries = { RETURN } \ + }, \ + }, \ + } \ + }; + +#define mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, RETURN, NAME, NPARAMS, ...) \ + _mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, NULL, __VA_ARGS__) + +#define mSCRIPT_DECLARE_DOC_STRUCT_VOID_METHOD(SCOPE, TYPE, NAME, NPARAMS, ...) \ + _mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 0, 0, NPARAMS, NULL, __VA_ARGS__) + +#define mSCRIPT_DECLARE_DOC_STRUCT_METHOD_WITH_DEFAULTS(SCOPE, TYPE, RETURN, NAME, NPARAMS, ...) \ + static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \ + _mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, _mIDENT(_mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME), __VA_ARGS__) \ + +#define mSCRIPT_DECLARE_DOC_STRUCT_VOID_METHOD_WITH_DEFAULTS(SCOPE, TYPE, NAME, NPARAMS, ...) \ + static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \ + _mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 0, 0, NPARAMS, _mIDENT(_mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME), __VA_ARGS__) \ + #define mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(TYPE, NAME) \ static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \ mSCRIPT_NO_DEFAULT, +#define mSCRIPT_DEFINE_DOC_STRUCT_BINDING_DEFAULTS(SCOPE, TYPE, NAME) \ + static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \ + mSCRIPT_NO_DEFAULT, + #define mSCRIPT_DEFINE_DEFAULTS_END } #define _mSCRIPT_DEFINE_STRUCT_BINDING(INIT_TYPE, TYPE, EXPORTED_NAME, NAME) { \ @@ -366,6 +421,8 @@ CXX_GUARD_START #define mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(GET, TYPE, _get, _get) #define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set) +#define mSCRIPT_DEFINE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME) mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(doc_ ## TYPE, NAME, NAME) + #define mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(TYPE, CAST_TYPE, MEMBER) { \ .type = mSCRIPT_CLASS_INIT_CAST_TO_MEMBER, \ .info = { \ @@ -435,6 +492,39 @@ CXX_GUARD_START } \ _mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NPARAMS, __VA_ARGS__) +#define _mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, NRET, RETURN, NPARAMS, ...) \ + static const struct mScriptType _mScriptDocType_ ## NAME = { \ + .base = mSCRIPT_TYPE_FUNCTION, \ + .name = SCOPE "::function::" #NAME, \ + .alloc = NULL, \ + .details = { \ + .function = { \ + .parameters = { \ + .count = NPARAMS, \ + .entries = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \ + .names = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \ + }, \ + .returnType = { \ + .count = NRET, \ + .entries = { RETURN } \ + }, \ + }, \ + } \ + }; \ + const struct mScriptValue _mScriptDoc_ ## NAME = { \ + .type = &_mScriptDocType_ ## NAME, \ + .refs = mSCRIPT_VALUE_UNREF, \ + .value = { \ + .copaque = NULL \ + } \ + } + +#define mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, RETURN, NPARAMS, ...) \ + _mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, __VA_ARGS__) + +#define mSCRIPT_DEFINE_DOC_VOID_FUNCTION(SCOPE, NAME, NPARAMS, ...) \ + _mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, 0, 0, NPARAMS, __VA_ARGS__) + #define mSCRIPT_MAKE(TYPE, VALUE) (struct mScriptValue) { \ .type = (mSCRIPT_TYPE_MS_ ## TYPE), \ .refs = mSCRIPT_VALUE_UNREF, \ diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 267f6b775..8b7503849 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -17,6 +17,8 @@ CXX_GUARD_START #define mSCRIPT_VALUE_UNREF -1 #define mSCRIPT_PARAMS_MAX 8 +#define mSCRIPT_VALUE_DOC_FUNCTION(NAME) (&_mScriptDoc_ ## NAME) + #define mSCRIPT_TYPE_C_S8 int8_t #define mSCRIPT_TYPE_C_U8 uint8_t #define mSCRIPT_TYPE_C_S16 int16_t @@ -100,6 +102,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper) #define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE) #define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE) +#define mSCRIPT_TYPE_MS_DS(STRUCT) (&mSTStruct_doc_ ## STRUCT) #define mSCRIPT_TYPE_CMP_GENERIC(TYPE0, TYPE1) (TYPE0 == TYPE1) #define mSCRIPT_TYPE_CMP_U8(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_U8, TYPE) diff --git a/src/script/context.c b/src/script/context.c index 84847983c..fa1a0b4a2 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -8,6 +8,8 @@ #include #endif +#define KEY_NAME_MAX 128 + struct mScriptFileInfo { const char* name; struct VFile* vf; @@ -302,6 +304,29 @@ const char* mScriptContextGetDocstring(struct mScriptContext* context, const cha return HashTableLookup(&context->docstrings, key); } +void mScriptEngineExportDocNamespace(struct mScriptEngineContext* ctx, const char* nspace, struct mScriptKVPair* values) { + struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + size_t i; + for (i = 0; values[i].key; ++i) { + struct mScriptValue* key = mScriptStringCreateFromUTF8(values[i].key); + mScriptTableInsert(table, key, values[i].value); + mScriptValueDeref(key); + } + HashTableInsert(&ctx->docroot, nspace, table); +} + +void mScriptEngineSetDocstring(struct mScriptEngineContext* ctx, const char* key, const char* docstring) { + char scopedKey[KEY_NAME_MAX]; + snprintf(scopedKey, sizeof(scopedKey), "%s::%s", ctx->engine->name, key); + HashTableInsert(&ctx->context->docstrings, scopedKey, (char*) docstring); +} + +const char* mScriptEngineGetDocstring(struct mScriptEngineContext* ctx, const char* key) { + char scopedKey[KEY_NAME_MAX]; + snprintf(scopedKey, sizeof(scopedKey), "%s::%s", ctx->engine->name, key); + return HashTableLookup(&ctx->context->docstrings, scopedKey); +} + bool mScriptContextLoadVF(struct mScriptContext* context, const char* name, struct VFile* vf) { struct mScriptFileInfo info = { .name = name, diff --git a/src/script/docgen.c b/src/script/docgen.c index ecf19a354..569d06e51 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -7,12 +7,33 @@ #include #include #include +#include struct mScriptContext context; struct Table types; FILE* out; +const struct mScriptType* const baseTypes[] = { + mSCRIPT_TYPE_MS_S8, + mSCRIPT_TYPE_MS_U8, + mSCRIPT_TYPE_MS_S16, + mSCRIPT_TYPE_MS_U16, + mSCRIPT_TYPE_MS_S32, + mSCRIPT_TYPE_MS_U32, + mSCRIPT_TYPE_MS_F32, + mSCRIPT_TYPE_MS_S64, + mSCRIPT_TYPE_MS_U64, + mSCRIPT_TYPE_MS_F64, + mSCRIPT_TYPE_MS_STR, + mSCRIPT_TYPE_MS_CHARP, + mSCRIPT_TYPE_MS_LIST, + mSCRIPT_TYPE_MS_TABLE, + mSCRIPT_TYPE_MS_WRAPPER, + NULL +}; + void explainValue(struct mScriptValue* value, const char* name, int level); +void explainValueScoped(struct mScriptValue* value, const char* name, const char* scope, int level); void explainType(struct mScriptType* type, int level); void addTypesFromTuple(const struct mScriptTypeTuple*); @@ -154,7 +175,7 @@ bool printval(const struct mScriptValue* value, char* buffer, size_t bufferSize) return false; } -void explainTable(struct mScriptValue* value, const char* name, int level) { +void explainTable(struct mScriptValue* value, const char* name, const char* scope, int level) { char indent[(level + 1) * 2 + 1]; memset(indent, ' ', sizeof(indent) - 1); indent[sizeof(indent) - 1] = '\0'; @@ -171,9 +192,9 @@ void explainTable(struct mScriptValue* value, const char* name, int level) { struct mScriptValue string; if (mScriptCast(mSCRIPT_TYPE_MS_CHARP, k, &string)) { snprintf(keyval, sizeof(keyval), "%s.%s", name, (const char*) string.value.opaque); - explainValue(v, keyval, level + 1); + explainValueScoped(v, keyval, scope, level + 1); } else { - explainValue(v, NULL, level + 1); + explainValueScoped(v, NULL, scope, level + 1); } } while (mScriptTableIteratorNext(value, &iter)); } @@ -250,6 +271,10 @@ void explainObject(struct mScriptValue* value, int level) { } void explainValue(struct mScriptValue* value, const char* name, int level) { + explainValueScoped(value, name, NULL, level); +} + +void explainValueScoped(struct mScriptValue* value, const char* name, const char* scope, int level) { char valstring[1024]; char indent[(level + 1) * 2 + 1]; memset(indent, ' ', sizeof(indent) - 1); @@ -260,7 +285,12 @@ void explainValue(struct mScriptValue* value, const char* name, int level) { const char* docstring = NULL; if (name) { - docstring = mScriptContextGetDocstring(&context, name); + if (scope) { + snprintf(valstring, sizeof(valstring), "%s::%s", scope, name); + docstring = mScriptContextGetDocstring(&context, valstring); + } else { + docstring = mScriptContextGetDocstring(&context, name); + } } if (docstring) { fprintf(out, "%scomment: \"%s\"\n", indent, docstring); @@ -269,7 +299,7 @@ void explainValue(struct mScriptValue* value, const char* name, int level) { switch (value->type->base) { case mSCRIPT_TYPE_TABLE: fprintf(out, "%svalue:\n", indent); - explainTable(value, name, level); + explainTable(value, name, scope, level); break; case mSCRIPT_TYPE_SINT: case mSCRIPT_TYPE_UINT: @@ -438,6 +468,35 @@ void explainCore(struct mCore* core) { mScriptContextDetachCore(&context); } +void initTypes(void) { + HashTableInit(&types, 0, NULL); + + size_t i; + for (i = 0; baseTypes[i]; ++i) { + addType(baseTypes[i]); + } +} + +void explainTypes(int level, const char* prefix) { + char indent[level * 2 + 1]; + memset(indent, ' ', sizeof(indent) - 1); + indent[sizeof(indent) - 1] = '\0'; + fprintf(out, "%stypes:\n", indent); + + struct TableIterator iter; + if (HashTableIteratorStart(&types, &iter)) { + do { + const char* name = HashTableIteratorGetKey(&types, &iter); + struct mScriptType* type = HashTableIteratorGetValue(&types, &iter); + if (prefix && !startswith(name, prefix)) { + continue; + } + fprintf(out, "%s %s:\n", indent, name); + explainType(type, level + 1); + } while (HashTableIteratorNext(&types, &iter)); + } +} + int main(int argc, char* argv[]) { out = stdout; if (argc > 1) { @@ -450,24 +509,10 @@ int main(int argc, char* argv[]) { mScriptContextInit(&context); mScriptContextAttachStdlib(&context); + mScriptContextAttachSocket(&context); mScriptContextSetTextBufferFactory(&context, NULL, NULL); - HashTableInit(&types, 0, NULL); - addType(mSCRIPT_TYPE_MS_S8); - addType(mSCRIPT_TYPE_MS_U8); - addType(mSCRIPT_TYPE_MS_S16); - addType(mSCRIPT_TYPE_MS_U16); - addType(mSCRIPT_TYPE_MS_S32); - addType(mSCRIPT_TYPE_MS_U32); - addType(mSCRIPT_TYPE_MS_F32); - addType(mSCRIPT_TYPE_MS_S64); - addType(mSCRIPT_TYPE_MS_U64); - addType(mSCRIPT_TYPE_MS_F64); - addType(mSCRIPT_TYPE_MS_STR); - addType(mSCRIPT_TYPE_MS_CHARP); - addType(mSCRIPT_TYPE_MS_LIST); - addType(mSCRIPT_TYPE_MS_TABLE); - addType(mSCRIPT_TYPE_MS_WRAPPER); + initTypes(); fputs("version:\n", out); fprintf(out, " string: \"%s\"\n", projectVersion); @@ -498,16 +543,28 @@ int main(int argc, char* argv[]) { explainCore(core); core->deinit(core); } - fputs("types:\n", out); - if (HashTableIteratorStart(&types, &iter)) { - do { - const char* name = HashTableIteratorGetKey(&types, &iter); - fprintf(out, " %s:\n", name); - struct mScriptType* type = HashTableIteratorGetValue(&types, &iter); - explainType(type, 1); - } while (HashTableIteratorNext(&types, &iter)); - } + explainTypes(0, NULL); + mScriptContextRegisterEngines(&context); + fputs("engines:\n", out); + if (HashTableIteratorStart(&context.engines, &iter)) { + do { + struct TableIterator rootIter; + struct mScriptEngineContext* engine = HashTableIteratorGetValue(&context.engines, &iter); + const char* name = HashTableIteratorGetKey(&context.engines, &iter); + fprintf(out, " %s:\n root:\n", name); + if (HashTableIteratorStart(&engine->docroot, &rootIter)) { + do { + const char* key = HashTableIteratorGetKey(&engine->docroot, &rootIter); + struct mScriptValue* value = HashTableIteratorGetValue(&engine->docroot, &rootIter); + fprintf(out, " %s:\n", key); + explainValueScoped(value, key, name, 3); + } while (HashTableIteratorNext(&engine->docroot, &rootIter)); + } + + explainTypes(2, name); + } while (HashTableIteratorNext(&context.engines, &iter)); + } HashTableDeinit(&types); mScriptContextDeinit(&context); return 0; diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 2a72172e9..78c1ed2ba 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -333,6 +333,8 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS lua_getglobal(luaContext->lua, "require"); luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); + HashTableInit(&luaContext->d.docroot, 0, (void (*)(void*)) mScriptValueDeref); + int status = luaL_dostring(luaContext->lua, _socketLuaSource); if (status) { mLOG(SCRIPT, ERROR, "Error in dostring while initializing sockets: %s\n", lua_tostring(luaContext->lua, -1)); @@ -369,6 +371,8 @@ void _luaDestroy(struct mScriptEngineContext* ctx) { luaL_unref(luaContext->lua, LUA_REGISTRYINDEX, luaContext->require); } lua_close(luaContext->lua); + + HashTableDeinit(&luaContext->d.docroot); free(luaContext); } From a12a391fd1eac5b1c0a70ac8d4df1d4e5b60ff26 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 26 Sep 2022 00:15:53 -0700 Subject: [PATCH 23/37] CMake: Don't include scripting.c in libretro build --- CMakeLists.txt | 4 ++++ src/core/CMakeLists.txt | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0c105964..5c0b987d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -866,6 +866,10 @@ if(ENABLE_EXTRA) list(APPEND SRC ${EXTRA_SRC}) endif() +if(ENABLE_SCRIPTING) + list(APPEND SRC ${CORE_SCRIPT_SRC}) +endif() + if(NOT SKIP_LIBRARY) if(NOT BUILD_STATIC AND NOT BUILD_SHARED) set(BUILD_SHARED ON) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index bd1f3ecf8..184445023 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -24,7 +24,7 @@ set(TEST_FILES test/core.c) if(ENABLE_SCRIPTING) - list(APPEND SOURCE_FILES + set(SCRIPTING_FILES scripting.c) if(USE_LUA) @@ -34,7 +34,9 @@ if(ENABLE_SCRIPTING) endif() source_group("mCore" FILES ${SOURCE_FILES}) +source_group("mCore scripting" FILES ${SCRIPTING_FILES}) source_group("mCore tests" FILES ${TEST_FILES}) export_directory(CORE SOURCE_FILES) +export_directory(CORE_SCRIPT SCRIPTING_FILES) export_directory(CORE_TEST TEST_FILES) From c06d38449d538df7e15b3c1bcd81571467e9f983 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 26 Sep 2022 00:20:09 -0700 Subject: [PATCH 24/37] Qt: Fix scripting disabled build --- src/platform/qt/Window.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 75a1e474e..bab25b1c2 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -50,7 +50,9 @@ #include "ReportView.h" #include "ROMInfo.h" #include "SaveConverter.h" +#ifdef ENABLE_SCRIPTING #include "scripting/ScriptingView.h" +#endif #include "SensorView.h" #include "ShaderSelector.h" #include "ShortcutController.h" From 55c2efa3ea6d9bb17b9a14344fa823779b093f7d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 27 Sep 2022 05:32:13 -0700 Subject: [PATCH 25/37] GB: Don't try to map a 0-byte SRAM (fixes #2668) --- CHANGES | 1 + src/gb/gb.c | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 442380f5d..c374c18fc 100644 --- a/CHANGES +++ b/CHANGES @@ -63,6 +63,7 @@ Other fixes: - FFmpeg: Fix GIF recording (fixes mgba.io/i/2393) - GB: Fix temporary saves - GB: Fix replacing the ROM crashing when accessing ROM base + - GB: Don't try to map a 0-byte SRAM (fixes mgba.io/i/2668) - GB, GBA: Save writeback-pending masked saves on unload (fixes mgba.io/i/2396) - mGUI: Fix FPS counter after closing menu - Qt: Fix some hangs when using the debugger console diff --git a/src/gb/gb.c b/src/gb/gb.c index 3f9fd59a6..dc3a37191 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -276,13 +276,17 @@ void GBResizeSram(struct GB* gb, size_t size) { vf->seek(vf, size, SEEK_SET); vf->write(vf, extdataBuffer, vfSize & 0xFF); } - gb->memory.sram = vf->map(vf, size, MAP_WRITE); - memset(&gb->memory.sram[vfSize], 0xFF, size - vfSize); + if (size) { + gb->memory.sram = vf->map(vf, size, MAP_WRITE); + memset(&gb->memory.sram[vfSize], 0xFF, size - vfSize); + } } else if (size > gb->sramSize || !gb->memory.sram) { if (gb->memory.sram) { vf->unmap(vf, gb->memory.sram, gb->sramSize); } - gb->memory.sram = vf->map(vf, size, MAP_WRITE); + if (size) { + gb->memory.sram = vf->map(vf, size, MAP_WRITE); + } } } else { if (gb->memory.sram) { @@ -296,9 +300,11 @@ void GBResizeSram(struct GB* gb, size_t size) { gb->sramVf = newVf; vf->truncate(vf, size); } - gb->memory.sram = vf->map(vf, size, MAP_READ); + if (size) { + gb->memory.sram = vf->map(vf, size, MAP_READ); + } } - if (gb->memory.sram == (void*) -1) { + if (!size || gb->memory.sram == (void*) -1) { gb->memory.sram = NULL; } } else if (size) { From 31f798748b1616e2b301eaa8fa903aec9b748008 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 27 Sep 2022 05:33:25 -0700 Subject: [PATCH 26/37] VFS: Early return NULL if attempting to map 0 bytes from a file --- CHANGES | 1 + src/platform/3ds/3ds-vfs.c | 3 +++ src/platform/psp2/sce-vfs.c | 3 +++ src/util/vfs/vfs-fd.c | 12 +++++++++++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c374c18fc..570c6f5ef 100644 --- a/CHANGES +++ b/CHANGES @@ -112,6 +112,7 @@ Misc: - Qt: Resume crashed game when loading a save state - SDL: Support exposing an axis directly as the gyro value (closes mgba.io/i/2531) - Windows: Attach to console if present + - VFS: Early return NULL if attempting to map 0 bytes from a file - Vita: Add bilinear filtering option (closes mgba.io/i/344) 0.9.3: (2021-12-17) diff --git a/src/platform/3ds/3ds-vfs.c b/src/platform/3ds/3ds-vfs.c index 5b6a8c9eb..f2aaeb84a 100644 --- a/src/platform/3ds/3ds-vfs.c +++ b/src/platform/3ds/3ds-vfs.c @@ -137,6 +137,9 @@ ssize_t _vf3dWrite(struct VFile* vf, const void* buffer, size_t size) { static void* _vf3dMap(struct VFile* vf, size_t size, int flags) { struct VFile3DS* vf3d = (struct VFile3DS*) vf; UNUSED(flags); + if (!size) { + return NULL; + } void* buffer = anonymousMemoryMap(size); if (buffer) { u32 sizeRead; diff --git a/src/platform/psp2/sce-vfs.c b/src/platform/psp2/sce-vfs.c index 138d7278c..621fd1f67 100644 --- a/src/platform/psp2/sce-vfs.c +++ b/src/platform/psp2/sce-vfs.c @@ -113,6 +113,9 @@ ssize_t _vfsceWrite(struct VFile* vf, const void* buffer, size_t size) { static void* _vfsceMap(struct VFile* vf, size_t size, int flags) { struct VFileSce* vfsce = (struct VFileSce*) vf; UNUSED(flags); + if (!size) { + return NULL; + } void* buffer = anonymousMemoryMap(size); if (buffer) { SceOff cur = sceIoLseek(vfsce->fd, 0, SEEK_CUR); diff --git a/src/util/vfs/vfs-fd.c b/src/util/vfs/vfs-fd.c index 54046aa10..2535bd0fb 100644 --- a/src/util/vfs/vfs-fd.c +++ b/src/util/vfs/vfs-fd.c @@ -134,6 +134,9 @@ ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size) { #ifdef _POSIX_MAPPED_FILES static void* _vfdMap(struct VFile* vf, size_t size, int flags) { struct VFileFD* vfd = (struct VFileFD*) vf; + if (!size) { + return NULL; + } int mmapFlags = MAP_PRIVATE; if (flags & MAP_WRITE) { mmapFlags = MAP_SHARED; @@ -153,6 +156,9 @@ static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { #elif defined(_WIN32) static void* _vfdMap(struct VFile* vf, size_t size, int flags) { struct VFileFD* vfd = (struct VFileFD*) vf; + if (!size) { + return NULL; + } int createFlags = PAGE_WRITECOPY; int mapFiles = FILE_MAP_COPY; if (flags & MAP_WRITE) { @@ -192,12 +198,16 @@ static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { #else static void* _vfdMap(struct VFile* vf, size_t size, int flags) { struct VFileFD* vfd = (struct VFileFD*) vf; + if (!size) { + vfd->writable = false; + return NULL; + } if (flags & MAP_WRITE) { vfd->writable = true; } void* mem = anonymousMemoryMap(size); if (!mem) { - return 0; + return NULL; } off_t pos = lseek(vfd->fd, 0, SEEK_CUR); From 0c33863e66baa3522acef2dbde7321375276b61c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 28 Sep 2022 04:27:33 -0700 Subject: [PATCH 27/37] Qt: Include cheats in bug report --- CHANGES | 1 + src/platform/qt/ReportView.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGES b/CHANGES index 570c6f5ef..5077b7b4c 100644 --- a/CHANGES +++ b/CHANGES @@ -110,6 +110,7 @@ Misc: - Qt: Change lossless setting to use WavPack audio - Qt: Use FFmpeg to convert additional camera formats, if available - Qt: Resume crashed game when loading a save state + - Qt: Include cheats in bug report - SDL: Support exposing an axis directly as the gyro value (closes mgba.io/i/2531) - Windows: Attach to console if present - VFS: Early return NULL if attempting to map 0 bytes from a file diff --git a/src/platform/qt/ReportView.cpp b/src/platform/qt/ReportView.cpp index b38483e7f..61a7a2cd1 100644 --- a/src/platform/qt/ReportView.cpp +++ b/src/platform/qt/ReportView.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -266,6 +267,17 @@ void ReportView::generateReport() { } addROMInfo(windowReport, controller.get()); + mCheatDevice* device = controller->cheatDevice(); + if (device) { + VFileDevice vf(VFileDevice::openMemory()); + mCheatSaveFile(device, vf); + vf.seek(0); + QByteArray cheats(vf.readAll()); + if (cheats.size()) { + addReport(QString("Cheats %1").arg(winId), QString::fromUtf8(cheats)); + } + } + if (m_ui.includeSave->isChecked() && !m_ui.includeState->isChecked()) { // Only do the save separately if savestates aren't enabled, to guarantee consistency mCore* core = controller->thread()->core; From 7719dd5ec4195fb4eb6f0b28a8a5e50f84c8a521 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 29 Sep 2022 02:52:57 -0700 Subject: [PATCH 28/37] Scripting: Fix member docstrings with newlines --- src/script/docgen.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/script/docgen.c b/src/script/docgen.c index 569d06e51..ba740e6aa 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -230,7 +230,12 @@ void explainClass(struct mScriptTypeClass* cls, int level) { case mSCRIPT_CLASS_INIT_INSTANCE_MEMBER: fprintf(out, "%s %s:\n", indent, details->info.member.name); if (docstring) { - fprintf(out, "%s comment: \"%s\"\n", indent, docstring); + if (strchr(docstring, '\n')) { + fprintf(out, "%s comment: |-\n", indent); + printchomp(docstring, level + 3); + } else { + fprintf(out, "%s comment: \"%s\"\n", indent, docstring); + } docstring = NULL; } fprintf(out, "%s type: %s\n", indent, details->info.member.type->name); From 91fb63c484635d795e8ee8dfc9ce2d145e009ec9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 29 Sep 2022 03:11:59 -0700 Subject: [PATCH 29/37] Scripting: Add "internal" marker to classes, exported to docgen --- include/mgba/script/macros.h | 4 ++++ include/mgba/script/types.h | 2 ++ src/script/docgen.c | 3 +++ src/script/socket.c | 1 + src/script/types.c | 3 +++ 5 files changed, 13 insertions(+) diff --git a/include/mgba/script/macros.h b/include/mgba/script/macros.h index 34b59e0e3..42710f9e8 100644 --- a/include/mgba/script/macros.h +++ b/include/mgba/script/macros.h @@ -233,6 +233,10 @@ CXX_GUARD_START } \ }, +#define mSCRIPT_DEFINE_INTERNAL { \ + .type = mSCRIPT_CLASS_INIT_INTERNAL \ +}, + #define _mSCRIPT_STRUCT_METHOD_POP(TYPE, S, NPARAMS, ...) \ _mCALL(_mCAT(mSCRIPT_POP_, _mSUCC_ ## NPARAMS), &frame->arguments, _mCOMMA_ ## NPARAMS(S(TYPE), __VA_ARGS__)); \ if (mScriptListSize(&frame->arguments)) { \ diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 8b7503849..470ed40ce 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -154,6 +154,7 @@ enum mScriptClassInitType { mSCRIPT_CLASS_INIT_DEINIT, mSCRIPT_CLASS_INIT_GET, mSCRIPT_CLASS_INIT_SET, + mSCRIPT_CLASS_INIT_INTERNAL, }; enum { @@ -245,6 +246,7 @@ struct mScriptTypeClass { const struct mScriptClassInitDetails* details; const struct mScriptType* parent; const char* docstring; + bool internal; struct Table instanceMembers; struct Table castToMembers; struct mScriptClassMember* alloc; // TODO diff --git a/src/script/docgen.c b/src/script/docgen.c index ba740e6aa..b088b7c96 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -208,6 +208,9 @@ void explainClass(struct mScriptTypeClass* cls, int level) { if (cls->parent) { fprintf(out, "%sparent: %s\n", indent, cls->parent->name); } + if (cls->internal) { + fprintf(out, "%sinternal: true\n", indent); + } if (cls->docstring) { if (strchr(cls->docstring, '\n')) { fprintf(out, "%scomment: |-\n", indent); diff --git a/src/script/socket.c b/src/script/socket.c index 588fa7006..6177a2d6c 100644 --- a/src/script/socket.c +++ b/src/script/socket.c @@ -204,6 +204,7 @@ mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, WSTR, recv, _mScriptSocketRecv, 1, mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, select, _mScriptSocketSelectOne, 1, S64, timeoutMillis); mSCRIPT_DEFINE_STRUCT(mScriptSocket) + mSCRIPT_DEFINE_INTERNAL mSCRIPT_DEFINE_CLASS_DOCSTRING("An internal implementation of a TCP network socket.") mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(mScriptSocket, close) mSCRIPT_DEFINE_DOCSTRING("Closes the socket. If the socket is already closed, this function does nothing.") diff --git a/src/script/types.c b/src/script/types.c index 8ce4df461..b63fed4a5 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -1088,6 +1088,9 @@ static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScript docstring = NULL; } break; + case mSCRIPT_CLASS_INIT_INTERNAL: + cls->internal = true; + break; } } } From 6fc8195177f362cb105ab8bdbc142120aad517ac Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 29 Sep 2022 03:12:11 -0700 Subject: [PATCH 30/37] Scripting: Style nit --- src/script/socket.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/script/socket.c b/src/script/socket.c index 6177a2d6c..818c758f6 100644 --- a/src/script/socket.c +++ b/src/script/socket.c @@ -224,7 +224,7 @@ mSCRIPT_DEFINE_STRUCT(mScriptSocket) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, connect) mSCRIPT_DEFINE_DOCSTRING( "Begins listening for incoming connections. The socket must have first been " - "bound with the `open` function." + "bound with the struct::Socket.open method." ) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, listen) mSCRIPT_DEFINE_DOCSTRING( @@ -235,7 +235,7 @@ mSCRIPT_DEFINE_STRUCT(mScriptSocket) mSCRIPT_DEFINE_DOCSTRING( "Reads data from a socket, up to the specified number of bytes. " "If the socket has been disconnected, this function returns an empty string. " - "Use `select` to test if data is available to be read." + "Use struct::Socket.select to test if data is available to be read." ) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, recv) mSCRIPT_DEFINE_DOCSTRING( @@ -245,7 +245,7 @@ mSCRIPT_DEFINE_STRUCT(mScriptSocket) ) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, select) mSCRIPT_DEFINE_DOCSTRING( - "One of the `SOCKERR` constants describing the last error on the socket." + "One of the C.SOCKERR constants describing the last error on the socket." ) mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptSocket, S32, error) mSCRIPT_DEFINE_END; From e4a5a3f2db5656aa7381f9f9edcd24a48a22f0ee Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 29 Sep 2022 03:27:26 -0700 Subject: [PATCH 31/37] Scripting: Fix value docstrings with newlines --- src/script/docgen.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/script/docgen.c b/src/script/docgen.c index b088b7c96..55d12eedc 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -301,7 +301,12 @@ void explainValueScoped(struct mScriptValue* value, const char* name, const char } } if (docstring) { - fprintf(out, "%scomment: \"%s\"\n", indent, docstring); + if (strchr(docstring, '\n')) { + fprintf(out, "%scomment: |-\n", indent); + printchomp(docstring, level + 1); + } else { + fprintf(out, "%scomment: \"%s\"\n", indent, docstring); + } } switch (value->type->base) { From 18074425772378c68e44a3ecb929ceace973bfbb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 25 Sep 2022 17:32:29 -0700 Subject: [PATCH 32/37] Scripting: Add documentation for Lua sockets --- src/script/engines/lua.c | 110 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 78c1ed2ba..37327ee54 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,9 @@ #endif #define MAX_KEY_SIZE 128 +#define LUA_NAME "lua" + +#define mSCRIPT_TYPE_MS_LUA_FUNC (&mSTLuaFunc) static struct mScriptEngineContext* _luaCreate(struct mScriptEngine2*, struct mScriptContext*); @@ -218,10 +222,84 @@ static const int _mScriptSocketNumErrors = sizeof(_mScriptSocketErrors) / sizeof #define lua_pushglobaltable(L) lua_pushvalue(L, LUA_GLOBALSINDEX) #endif +const struct mScriptType mSTLuaFunc; + +mSCRIPT_DECLARE_DOC_STRUCT(LUA_NAME, socket); +mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, S64, add, 2, STR, event, LUA_FUNC, callback); +mSCRIPT_DECLARE_DOC_STRUCT_VOID_METHOD(LUA_NAME, socket, remove, 1, S64, cbid); +mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, S32, bind, 2, STR, address, U16, port); +mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, S32, connect, 2, STR, address, U16, port); +mSCRIPT_DECLARE_DOC_STRUCT_METHOD_WITH_DEFAULTS(LUA_NAME, socket, S32, listen, 1, S32, backlog); +mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, DS(socket), accept, 0); +mSCRIPT_DECLARE_DOC_STRUCT_METHOD_WITH_DEFAULTS(LUA_NAME, socket, S32, send, 3, STR, data, S64, i, S64, j); +mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, STR, receive, 1, S64, maxBytes); +mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, BOOL, hasdata, 0); +mSCRIPT_DECLARE_DOC_STRUCT_VOID_METHOD(LUA_NAME, socket, poll, 0); + +mSCRIPT_DEFINE_DOC_STRUCT(LUA_NAME, socket) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "An instance of a TCP socket. Most of these functions will return two values if an error occurs; " + "the first value is `nil` and the second value is an error string from socket.ERRORS" + ) + mSCRIPT_DEFINE_DOCSTRING( + "Add a callback for a named event. The returned id can be used to remove it later. " + "Events get checked once per frame but can be checked manually using " LUA_NAME "::struct::socket.poll. " + "The following callbacks are defined:\n\n" + "- **received**: New data has been received and can be read\n" + "- **error**: An error has occurred on the socket\n" + ) + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, add) + mSCRIPT_DEFINE_DOCSTRING("Remove a callback with the previously returned id") + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, remove) + mSCRIPT_DEFINE_DOCSTRING("Creates a new socket for an incoming connection from a listening server socket") + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, accept) + mSCRIPT_DEFINE_DOCSTRING("Bind the socket to a specific interface and port. Use `nil` for `address` to bind to all interfaces") + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, bind) + mSCRIPT_DEFINE_DOCSTRING( + "Opens a TCP connection to the specified address and port.\n\n" + "**Caution:** This is a blocking call. The emulator will not respond until " + "the connection either succeeds or fails" + ) + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, connect) + mSCRIPT_DEFINE_DOCSTRING( + "Begins listening for incoming connections. The socket must have first been " + "bound with the " LUA_NAME "::struct::socket.bind function" + ) + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, listen) + mSCRIPT_DEFINE_DOCSTRING( + "Writes a string to the socket. If `i` and `j` are provided, they have the same semantics " + "as the parameters to `string.sub` to write a substring. Returns the last index written" + ) + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, send) + mSCRIPT_DEFINE_DOCSTRING( + "Read up to `maxBytes` bytes from the socket and return them. " + "If the socket has been disconnected or an error occurs, it will return `nil, error` instead" + ) + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, receive) + mSCRIPT_DEFINE_DOCSTRING("Check if a socket has data ready to receive, and return true if so") + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, hasdata) + mSCRIPT_DEFINE_DOCSTRING("Manually check for events on this socket and dispatch associated callbacks") + mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, poll) +mSCRIPT_DEFINE_END; + +mSCRIPT_DEFINE_DOC_STRUCT_BINDING_DEFAULTS(LUA_NAME, socket, listen) + mSCRIPT_S32(1), +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_DOC_STRUCT_BINDING_DEFAULTS(LUA_NAME, socket, send) + mSCRIPT_NO_DEFAULT, + mSCRIPT_S64(0), + mSCRIPT_S64(0), +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_DOC_FUNCTION(LUA_NAME, socket_tcp, DS(socket), 0); +mSCRIPT_DEFINE_DOC_FUNCTION(LUA_NAME, socket_bind, DS(socket), 2, STR, address, U16, port); +mSCRIPT_DEFINE_DOC_FUNCTION(LUA_NAME, socket_connect, DS(socket), 2, STR, address, U16, port); + const struct mScriptType mSTLuaFunc = { .base = mSCRIPT_TYPE_FUNCTION, .size = 0, - .name = "lua-" LUA_VERSION_ONLY "::function", + .name = LUA_NAME "::function", .details = { .function = { .parameters = { @@ -256,7 +334,7 @@ static struct mScriptEngineLua { struct mScriptEngine2 d; } _engineLua = { .d = { - .name = "lua-" LUA_VERSION_ONLY, + .name = LUA_NAME, .init = NULL, .deinit = NULL, .create = _luaCreate @@ -340,6 +418,7 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS mLOG(SCRIPT, ERROR, "Error in dostring while initializing sockets: %s\n", lua_tostring(luaContext->lua, -1)); lua_pop(luaContext->lua, 1); } else { + struct mScriptValue* errors = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); int i; lua_getglobal(luaContext->lua, "socket"); lua_getfield(luaContext->lua, -1, "ERRORS"); @@ -347,12 +426,39 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS const struct _mScriptSocketError* err = &_mScriptSocketErrors[i]; if (err->message) { lua_pushstring(luaContext->lua, err->message); + struct mScriptValue* key = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32); + key->value.s32 = err->err; + struct mScriptValue* message = mScriptStringCreateFromASCII(err->message); + mScriptTableInsert(errors, key, message); + mScriptValueDeref(key); + mScriptValueDeref(message); } else { lua_pushnil(luaContext->lua); } lua_seti(luaContext->lua, -2, err->err); } lua_pop(luaContext->lua, 2); + + mScriptEngineExportDocNamespace(&luaContext->d, "socket", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(ERRORS, errors), + mSCRIPT_KV_PAIR(tcp, mSCRIPT_VALUE_DOC_FUNCTION(socket_tcp)), + mSCRIPT_KV_PAIR(bind, mSCRIPT_VALUE_DOC_FUNCTION(socket_bind)), + mSCRIPT_KV_PAIR(connect, mSCRIPT_VALUE_DOC_FUNCTION(socket_connect)), + mSCRIPT_KV_SENTINEL + }); + mScriptValueDeref(errors); + mScriptEngineSetDocstring(&luaContext->d, "socket", "A basic TCP socket library"); + mScriptEngineSetDocstring(&luaContext->d, "socket.ERRORS", + "Error strings corresponding to the C.SOCKERR error codes, indexed both by name and by value"); + mScriptEngineSetDocstring(&luaContext->d, "socket.tcp", + "Create a new TCP socket, for use with either " LUA_NAME "::struct::socket.bind or " LUA_NAME "::struct::socket.connect later"); + mScriptEngineSetDocstring(&luaContext->d, "socket.bind", + "Create and bind a new socket to a specific interface and port. " + "Use `nil` for `address` to bind to all interfaces"); + mScriptEngineSetDocstring(&luaContext->d, "socket.connect", + "Create and return a new TCP socket with a connection to the specified address and port.\n\n" + "**Caution:** This is a blocking call. The emulator will not respond until " + "the connection either succeeds or fails"); } return &luaContext->d; From 9adad40b1ce14b00a19a8544ba0742f9bf3342fb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 2 Oct 2022 17:18:19 -0700 Subject: [PATCH 33/37] GB Audio: Fix changing channel properties mid-frame (fixes #2655) --- src/gb/audio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gb/audio.c b/src/gb/audio.c index 8adc9335f..0b6a03ecb 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -377,11 +377,13 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR50(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2); audio->volumeRight = GBRegisterNR50GetVolumeRight(value); audio->volumeLeft = GBRegisterNR50GetVolumeLeft(value); } void GBAudioWriteNR51(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2); audio->ch1Right = GBRegisterNR51GetCh1Right(value); audio->ch2Right = GBRegisterNR51GetCh2Right(value); audio->ch3Right = GBRegisterNR51GetCh3Right(value); From 168c9cb8d677d7f5898fd631530ddd9b67dd484c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Oct 2022 15:54:51 -0700 Subject: [PATCH 34/37] Debugger: platformCommands should be able to be NULL --- src/debugger/cli-debugger.c | 26 ++++++++++++++++++-------- src/sm83/debugger/cli-debugger.c | 6 +----- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index e3897cd3b..7c827905a 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -326,16 +326,24 @@ static void _printHelp(struct CLIDebugger* debugger, struct CLIDebugVector* dv) debugger->backend->printf(debugger->backend, "Generic commands:\n"); _printCommands(debugger, _debuggerCommands, _debuggerCommandAliases); if (debugger->system) { - debugger->backend->printf(debugger->backend, "\n%s commands:\n", debugger->system->platformName); - _printCommands(debugger, debugger->system->platformCommands, debugger->system->platformCommandAliases); - debugger->backend->printf(debugger->backend, "\n%s commands:\n", debugger->system->name); - _printCommands(debugger, debugger->system->commands, debugger->system->commandAliases); + if (debugger->system->platformCommands) { + debugger->backend->printf(debugger->backend, "\n%s commands:\n", debugger->system->platformName); + _printCommands(debugger, debugger->system->platformCommands, debugger->system->platformCommandAliases); + } + if (debugger->system->commands) { + debugger->backend->printf(debugger->backend, "\n%s commands:\n", debugger->system->name); + _printCommands(debugger, debugger->system->commands, debugger->system->commandAliases); + } } } else { _printCommandSummary(debugger, dv->charValue, _debuggerCommands, _debuggerCommandAliases); if (debugger->system) { - _printCommandSummary(debugger, dv->charValue, debugger->system->platformCommands, debugger->system->platformCommandAliases); - _printCommandSummary(debugger, dv->charValue, debugger->system->commands, debugger->system->commandAliases); + if (debugger->system->platformCommands) { + _printCommandSummary(debugger, dv->charValue, debugger->system->platformCommands, debugger->system->platformCommandAliases); + } + if (debugger->system->commands) { + _printCommandSummary(debugger, dv->charValue, debugger->system->commands, debugger->system->commandAliases); + } } } } @@ -981,8 +989,10 @@ bool CLIDebuggerRunCommand(struct CLIDebugger* debugger, const char* line, size_ } int result = _tryCommands(debugger, _debuggerCommands, _debuggerCommandAliases, line, cmdLength, args, count - cmdLength - 1); if (result < 0 && debugger->system) { - result = _tryCommands(debugger, debugger->system->commands, debugger->system->commandAliases, line, cmdLength, args, count - cmdLength - 1); - if (result < 0) { + if (debugger->system->commands) { + result = _tryCommands(debugger, debugger->system->commands, debugger->system->commandAliases, line, cmdLength, args, count - cmdLength - 1); + } + if (result < 0 && debugger->system->platformCommands) { result = _tryCommands(debugger, debugger->system->platformCommands, debugger->system->platformCommandAliases, line, cmdLength, args, count - cmdLength - 1); } } diff --git a/src/sm83/debugger/cli-debugger.c b/src/sm83/debugger/cli-debugger.c index 1c3d2a473..dfc56dc23 100644 --- a/src/sm83/debugger/cli-debugger.c +++ b/src/sm83/debugger/cli-debugger.c @@ -17,10 +17,6 @@ static void _printStatus(struct CLIDebuggerSystem*); static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVector* dv); static uint16_t _printLine(struct CLIDebugger* debugger, uint16_t address, int segment); -static struct CLIDebuggerCommandSummary _sm83Commands[] = { - { 0, 0, 0, 0 } -}; - static inline void _printFlags(struct CLIDebuggerBackend* be, union FlagRegister f) { be->printf(be, "F: [%c%c%c%c]\n", f.z ? 'Z' : '-', @@ -109,6 +105,6 @@ void SM83CLIDebuggerCreate(struct CLIDebuggerSystem* debugger) { debugger->printStatus = _printStatus; debugger->disassemble = _disassemble; debugger->platformName = "SM83"; - debugger->platformCommands = _sm83Commands; + debugger->platformCommands = NULL; debugger->platformCommandAliases = NULL; } From a56bfab48e1ef8e9280a11191b3fd4f2f502d2f6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Oct 2022 23:07:14 -0700 Subject: [PATCH 35/37] Qt: Fix OSD being off by default --- src/platform/qt/Window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index bab25b1c2..7186ff175 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1867,7 +1867,7 @@ void Window::setupOptions() { ConfigOption* showOSD = m_config->addOption("showOSD"); showOSD->connect([this](const QVariant& value) { - if (m_display) { + if (m_display && !value.isNull()) { m_display->showOSDMessages(value.toBool()); } }, this); From e2e22e22187223c61f20099c150880bf0a1e8177 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 6 Oct 2022 00:23:55 -0700 Subject: [PATCH 36/37] Res: Update patrons for October --- res/patrons.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/res/patrons.txt b/res/patrons.txt index 4f2f167bd..c9346ce64 100644 --- a/res/patrons.txt +++ b/res/patrons.txt @@ -1,13 +1,16 @@ Akatsuki +AVjoyu__Chan Benedikt Feih Brandon Emily A. Bellows +G gocha Jaime J. Denizard MichaelK__ Miras Absar Petru-Sebastian Toader -SquidHominid +Stevoisiak Tyler Jenkins William K. Leung Zach +Zhongchao Qian From bb711d311fdfd0bcd2e0925176090e3ea2c2b07e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 6 Oct 2022 02:57:26 -0700 Subject: [PATCH 37/37] GBA Savedata: Store RTC data in savegames (closes #240) --- CHANGES | 3 +- include/mgba/internal/gba/cart/gpio.h | 4 +- include/mgba/internal/gba/savedata.h | 10 +++++ include/mgba/internal/gba/serialize.h | 3 +- src/gba/cart/gpio.c | 6 +++ src/gba/gba.c | 1 + src/gba/overrides.c | 1 + src/gba/savedata.c | 53 +++++++++++++++++++++++++++ 8 files changed, 78 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 5077b7b4c..4c4bf3547 100644 --- a/CHANGES +++ b/CHANGES @@ -87,8 +87,9 @@ Misc: - GBA: Automatically skip BIOS if ROM has invalid logo - GBA: Refine multiboot detection (fixes mgba.io/i/2192) - GBA Cheats: Implement "never" type codes (closes mgba.io/i/915) - - GBA Memory: Implement adjustable EWRAM waitstates (closes mgba.io/i/1276) - GBA DMA: Enhanced logging (closes mgba.io/i/2454) + - GBA Memory: Implement adjustable EWRAM waitstates (closes mgba.io/i/1276) + - GBA Savedata: Store RTC data in savegames (closes mgba.io/i/240) - GBA Video: Implement layer placement for OpenGL renderer (fixes mgba.io/i/1962) - GBA Video: Fix highlighting for sprites with mid-frame palette changes - mGUI: Add margin to right-aligned menu text (fixes mgba.io/i/871) diff --git a/include/mgba/internal/gba/cart/gpio.h b/include/mgba/internal/gba/cart/gpio.h index fa90aed7c..86ddc941f 100644 --- a/include/mgba/internal/gba/cart/gpio.h +++ b/include/mgba/internal/gba/cart/gpio.h @@ -42,7 +42,7 @@ enum GPIODirection { GPIO_READ_WRITE = 1 }; -DECL_BITFIELD(RTCControl, uint32_t); +DECL_BITFIELD(RTCControl, uint8_t); DECL_BIT(RTCControl, MinIRQ, 3); DECL_BIT(RTCControl, Hour24, 6); DECL_BIT(RTCControl, Poweroff, 7); @@ -69,6 +69,8 @@ struct GBARTC { RTCCommandData command; RTCControl control; uint8_t time[7]; + time_t lastLatch; + time_t offset; }; DECL_BITFIELD(GPIOPin, uint16_t); diff --git a/include/mgba/internal/gba/savedata.h b/include/mgba/internal/gba/savedata.h index 7f0b84c83..102c23041 100644 --- a/include/mgba/internal/gba/savedata.h +++ b/include/mgba/internal/gba/savedata.h @@ -72,6 +72,7 @@ struct GBASavedata { uint8_t* data; enum SavedataCommand command; struct VFile* vf; + struct GBACartridgeHardware* gpio; int mapMode; bool maskWriteback; @@ -93,6 +94,12 @@ struct GBASavedata { enum FlashStateMachine flashState; }; +struct GBASavedataRTCBuffer { + uint8_t time[7]; + uint8_t control; + uint64_t lastLatch; +}; + void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf); void GBASavedataDeinit(struct GBASavedata* savedata); @@ -116,6 +123,9 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32 void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount); +void GBASavedataRTCRead(struct GBASavedata* savedata); +void GBASavedataRTCWrite(struct GBASavedata* savedata); + struct GBASerializedState; void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state); void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state); diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index cb1a3b384..ec44dc8fd 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -348,7 +348,8 @@ struct GBASerializedState { int32_t rtcBits; int32_t rtcCommandActive; RTCCommandData rtcCommand; - RTCControl rtcControl; + uint8_t rtcControl; + uint8_t reserved[3]; uint8_t time[7]; uint8_t devices; uint16_t gyroSample; diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index 6f7fe15e9..0bb7f3429 100644 --- a/src/gba/cart/gpio.c +++ b/src/gba/cart/gpio.c @@ -100,6 +100,9 @@ void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) { hw->rtc.command = 0; hw->rtc.control = 0x40; memset(hw->rtc.time, 0, sizeof(hw->rtc.time)); + + hw->rtc.lastLatch = 0; + hw->rtc.offset = 0; } void _readPins(struct GBACartridgeHardware* hw) { @@ -278,6 +281,9 @@ void _rtcUpdateClock(struct GBACartridgeHardware* hw) { } else { t = time(0); } + hw->rtc.lastLatch = t; + t -= hw->rtc.offset; + struct tm date; localtime_r(&t, &date); hw->rtc.time[0] = _rtcBCD(date.tm_year - 100); diff --git a/src/gba/gba.c b/src/gba/gba.c index 70da8e345..bd0353d0c 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -77,6 +77,7 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) { gba->memory.savedata.timing = &gba->timing; gba->memory.savedata.vf = NULL; gba->memory.savedata.realVf = NULL; + gba->memory.savedata.gpio = &gba->memory.hw; GBASavedataInit(&gba->memory.savedata, NULL); gba->video.p = gba; diff --git a/src/gba/overrides.c b/src/gba/overrides.c index a0593a90e..e15db50d8 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -341,6 +341,7 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri if (override->hardware & HW_RTC) { GBAHardwareInitRTC(&gba->memory.hw); + GBASavedataRTCRead(&gba->memory.savedata); } if (override->hardware & HW_GYRO) { diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 752d3874c..47d2b37ee 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -575,6 +575,7 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) { if (savedata->mapMode & MAP_WRITE) { size_t size = GBASavedataSize(savedata); if (savedata->data && savedata->vf->sync(savedata->vf, savedata->data, size)) { + GBASavedataRTCWrite(savedata); mLOG(GBA_SAVE, INFO, "Savedata synced"); } else { mLOG(GBA_SAVE, INFO, "Savedata failed to sync!"); @@ -583,6 +584,58 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) { } } +void GBASavedataRTCWrite(struct GBASavedata* savedata) { + if (!(savedata->gpio->devices & HW_RTC)) { + return; + } + + struct GBASavedataRTCBuffer buffer; + + memcpy(&buffer.time, savedata->gpio->rtc.time, 7); + buffer.control = savedata->gpio->rtc.control; + STORE_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch); + + size_t size = GBASavedataSize(savedata) & ~0xFF; + savedata->vf->seek(savedata->vf, size, SEEK_SET); + savedata->vf->write(savedata->vf, &buffer, sizeof(buffer)); +} + +static uint8_t _unBCD(uint8_t byte) { + return (byte >> 4) * 10 + (byte & 0xF); +} + +void GBASavedataRTCRead(struct GBASavedata* savedata) { + struct GBASavedataRTCBuffer buffer; + + size_t size = GBASavedataSize(savedata) & ~0xFF; + savedata->vf->seek(savedata->vf, size, SEEK_SET); + size = savedata->vf->read(savedata->vf, &buffer, sizeof(buffer)); + if (size < sizeof(buffer)) { + return; + } + + memcpy(savedata->gpio->rtc.time, &buffer.time, 7); + + // Older FlashGBX sets this to 0x01 instead of the control flag. + // Since that bit is invalid on hardware, we can check for != 0x01 + // to see if it's a valid value instead of just a filler value. + if (buffer.control != 1) { + savedata->gpio->rtc.control = buffer.control; + } + LOAD_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch); + + struct tm date; + date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100; + date.tm_mon = _unBCD(savedata->gpio->rtc.time[1]) - 1; + date.tm_mday = _unBCD(savedata->gpio->rtc.time[2]); + date.tm_hour = _unBCD(savedata->gpio->rtc.time[4]); + date.tm_min = _unBCD(savedata->gpio->rtc.time[5]); + date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]); + date.tm_isdst = -1; + + savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - mktime(&date); +} + void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) { state->savedata.type = savedata->type; state->savedata.command = savedata->command;