diff --git a/CHANGES b/CHANGES index 25bab3134..fe7361677 100644 --- a/CHANGES +++ b/CHANGES @@ -66,7 +66,9 @@ Emulation fixes: - GB Audio: Fix APU re-enable timing glitch - GB I/O: Fix writing to WAVE RAM behavior (fixes mgba.io/i/1334) - GB Memory: Add cursory cartridge open bus emulation (fixes mgba.io/i/2032) + - GB MBC: Fix edge case with Pocket Cam register accesses (fixes mgba.io/i/2557) - GB Serialize: Fix loading MBC1 states that affect bank 0 (fixes mgba.io/i/2402) + - GB SIO: Fix bidirectional transfer starting (fixes mgba.io/i/2290) - GB Video: Draw SGB border pieces that overlap GB graphics (fixes mgba.io/i/1339) - GBA: Improve timing when not booting from BIOS - GBA: Fix expected entry point for multiboot ELFs (fixes mgba.io/i/2450) @@ -85,6 +87,7 @@ Emulation fixes: - GBA Video: Fix rare crash in modes 3-5 - GBA Video: Fix sprites with mid-frame palette changes in GL (fixes mgba.io/i/2476) - GBA Video: Fix OBJ tile wrapping with 2D char mapping (fixes mgba.io/i/2443) + - GBA Video: Fix horizontal lines in GL when charbase is changed (fixes mgba.io/i/1631) Other fixes: - ARM: Disassemble Thumb mov pseudo-instruction properly - Core: Don't attempt to restore rewind diffs past start of rewind @@ -98,6 +101,7 @@ Other fixes: - Qt: Fix some hangs when using the debugger console - Qt: Fix crash when clicking past last tile in viewer - Qt: Fix preloading for ROM replacing + - Qt: Fix screen not displaying on Wayland (fixes mgba.io/i/2190) - VFS: Failed file mapping should return NULL on POSIX Misc: - Core: Suspend runloop when a core crashes @@ -129,6 +133,7 @@ Misc: - Qt: Add e-Card passing to the command line (closes mgba.io/i/2474) - 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) + - 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/interface.h b/include/mgba/core/interface.h index b9c688351..f3fc8e6fb 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -195,6 +195,11 @@ struct mAVStream { void (*videoFrameRateChanged)(struct mAVStream*, unsigned numerator, unsigned denominator); }; +struct mStereoSample { + int16_t left; + int16_t right; +}; + struct mKeyCallback { uint16_t (*readKeys)(struct mKeyCallback*); bool requireOpposingDirections; diff --git a/include/mgba/internal/gba/audio.h b/include/mgba/internal/gba/audio.h index 65336b299..8b212dd6a 100644 --- a/include/mgba/internal/gba/audio.h +++ b/include/mgba/internal/gba/audio.h @@ -11,6 +11,7 @@ CXX_GUARD_START #include +#include #include #include #include @@ -79,14 +80,16 @@ struct GBAAudio { bool enable; size_t samples; - unsigned sampleRate; - GBARegisterSOUNDBIAS soundbias; struct GBAAudioMixer* mixer; bool externalMixing; int32_t sampleInterval; + int32_t lastSample; + int sampleIndex; + struct mStereoSample currentSamples[GBA_MAX_SAMPLES]; + bool forceDisableChA; bool forceDisableChB; int masterVolume; @@ -94,11 +97,6 @@ struct GBAAudio { struct mTimingEvent sampleEvent; }; -struct GBAStereoSample { - int16_t left; - int16_t right; -}; - struct GBAMP2kADSR { uint8_t attack; uint8_t decay; @@ -278,7 +276,7 @@ struct GBAAudioMixer { double tempo; double frame; - struct GBAStereoSample last; + struct mStereoSample last; }; void GBAAudioInit(struct GBAAudio* audio, size_t samples); @@ -309,6 +307,8 @@ uint32_t GBAAudioReadWaveRAM(struct GBAAudio* audio, int address); uint32_t GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value); void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles); +void GBAAudioSample(struct GBAAudio* audio, int32_t timestamp); + struct GBASerializedState; void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state); void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state); diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index 1a972ada8..9e022f6f5 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -49,6 +49,7 @@ struct GBAVideoGLBackground { int enabled; unsigned priority; uint32_t charBase; + uint32_t oldCharBase; int mosaic; int multipalette; uint32_t screenBase; @@ -99,10 +100,12 @@ enum { GBA_GL_BG_TRANSFORM, GBA_GL_BG_RANGE, GBA_GL_BG_MOSAIC, + GBA_GL_BG_OLDCHARBASE, GBA_GL_OBJ_VRAM = 2, GBA_GL_OBJ_PALETTE, GBA_GL_OBJ_CHARBASE, + GBA_GL_OBJ_TILE, GBA_GL_OBJ_STRIDE, GBA_GL_OBJ_LOCALPALETTE, GBA_GL_OBJ_INFLAGS, diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index f3ed332dc..07223ea3f 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -71,7 +71,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | 0x00188 - 0x0018B: Next event * 0x0018C - 0x001AB: Audio FIFO 1 * 0x001AC - 0x001CB: Audio FIFO 2 - * 0x001CC - 0x001DF: Audio miscellaneous state + * 0x001CC - 0x001EF: Audio miscellaneous state * | 0x001CC - 0x001CF: Channel A internal audio samples * | 0x001D0 - 0x001D3: Channel B internal audio samples * | 0x001D4 - 0x001D7: Next sample @@ -104,9 +104,13 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bit 3: Is channel 3's memory readable? * | bit 4: Skip frame * | bits 5 - 7: Reserved - * 0x001E0 - 0x001FF: Video miscellaneous state - * | 0x001E0 - 0x001E3: Next event - * | 0x001E4 - 0x001F7: Reserved + * | 0x001E0 - 0x001E3: Last sample + * | 0x001E4 - 0x001E7: Additional audio flags + * | bits 0 - 3: Current sample index + * | 0x001E8 - 0x001EF: Reserved + * 0x001F0 - 0x001FF: Video miscellaneous state + * | 0x001F0 - 0x001F3: Reserved + * | 0x001F4 - 0x001F7: Next event * | 0x001F8 - 0x001FB: Miscellaneous flags * | 0x001FC - 0x001FF: Frame counter * 0x00200 - 0x00213: Timer 0 @@ -227,7 +231,8 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * 0x00368 - 0x0036F: Reserved (leave zero) * 0x00370 - 0x0037F: Audio FIFO A samples * 0x00380 - 0x0038F: Audio FIFO B samples - * 0x00390 - 0x003FF: Reserved (leave zero) + * 0x00390 - 0x003CF: Audio rendered samples + * 0x003D0 - 0x003FF: Reserved (leave zero) * 0x00400 - 0x007FF: I/O memory * 0x00800 - 0x00BFF: Palette * 0x00C00 - 0x00FFF: OAM @@ -243,6 +248,9 @@ DECL_BITS(GBASerializedAudioFlags, FIFOSamplesB, 2, 3); // Yay legacy? DECL_BITS(GBASerializedAudioFlags, FIFOInternalSamplesA, 5, 2); DECL_BITS(GBASerializedAudioFlags, FIFOSamplesA, 7, 3); +DECL_BITFIELD(GBASerializedAudioFlags2, uint32_t); +DECL_BITS(GBASerializedAudioFlags2, SampleIndex, 0, 4); + DECL_BITFIELD(GBASerializedVideoFlags, uint32_t); DECL_BITS(GBASerializedVideoFlags, Mode, 0, 2); @@ -303,11 +311,14 @@ struct GBASerializedState { int8_t sampleB; GBASerializedAudioFlags gbaFlags; GBSerializedAudioFlags flags; + int32_t lastSample; + GBASerializedAudioFlags2 gbaFlags2; + int32_t reserved[2]; } audio; struct { + int32_t reserved; int32_t nextEvent; - int32_t reserved[5]; GBASerializedVideoFlags flags; uint32_t frameCounter; } video; @@ -384,14 +395,16 @@ struct GBASerializedState { int32_t biosStall; uint32_t matrixMappings[16]; - uint32_t reservedMatrix[2]; + uint32_t reservedMatrix[2]; - struct { - int8_t chA[16]; - int8_t chB[16]; - } samples; + struct { + int8_t chA[16]; + int8_t chB[16]; + } samples; - uint32_t reserved[28]; + struct mStereoSample currentSamples[16]; + + uint32_t reserved[12]; uint16_t io[SIZE_IO >> 1]; uint16_t pram[SIZE_PALETTE_RAM >> 1]; diff --git a/src/gb/audio.c b/src/gb/audio.c index 47911f794..94a1c40c4 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -11,6 +11,9 @@ #include #include #include +#ifdef M_CORE_GBA +#include +#endif #ifdef __3DS__ #define blip_add_delta blip_add_delta_fast @@ -69,7 +72,6 @@ void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAu audio->timingFactor = 2; } - audio->frameEvent.context = audio; audio->frameEvent.name = "GB Audio Frame Sequencer"; audio->frameEvent.callback = _updateFrame; audio->frameEvent.priority = 0x10; @@ -85,14 +87,10 @@ void GBAudioDeinit(struct GBAudio* audio) { } void GBAudioReset(struct GBAudio* audio) { - mTimingDeschedule(audio->timing, &audio->frameEvent); mTimingDeschedule(audio->timing, &audio->sampleEvent); if (audio->style != GB_AUDIO_GBA) { mTimingSchedule(audio->timing, &audio->sampleEvent, 0); } - if (audio->style == GB_AUDIO_GBA) { - mTimingSchedule(audio->timing, &audio->frameEvent, 0); - } audio->ch1 = (struct GBAudioSquareChannel) { .sweep = { .time = 8 }, .envelope = { .dead = 2 } }; audio->ch2 = (struct GBAudioSquareChannel) { .envelope = { .dead = 2 } }; audio->ch3 = (struct GBAudioWaveChannel) { .bank = 0 }; @@ -458,11 +456,12 @@ void GBAudioWriteNR52(struct GBAudio* audio, uint8_t value) { } void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate) { - struct GBAudio* audio = user; - GBAudioUpdateFrame(audio); - if (audio->style == GB_AUDIO_GBA) { - mTimingSchedule(timing, &audio->frameEvent, audio->timingFactor * FRAME_CYCLES - cyclesLate); - } +#ifdef M_CORE_GBA + struct GBAAudio* audio = user; + GBAAudioSample(audio, mTimingCurrentTime(timing)); + mTimingSchedule(timing, &audio->psg.frameEvent, audio->psg.timingFactor * FRAME_CYCLES - cyclesLate); + GBAudioUpdateFrame(&audio->psg); +#endif } void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 9e8724964..282891cfe 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -1402,11 +1402,16 @@ void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) { if (value < 0x10) { GBMBCSwitchSramBank(gb, value); memory->mbcState.pocketCam.registersActive = false; + memory->directSramAccess = true; } else { memory->mbcState.pocketCam.registersActive = true; + memory->directSramAccess = false; } break; case 0x5: + if (!memory->mbcState.pocketCam.registersActive) { + break; + } address &= 0x7F; if (address == 0 && value & 1) { value &= 6; // TODO: Timing diff --git a/src/gb/sio/lockstep.c b/src/gb/sio/lockstep.c index d739140e5..4d1f1f549 100644 --- a/src/gb/sio/lockstep.c +++ b/src/gb/sio/lockstep.c @@ -157,7 +157,7 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) { } } // Tell the other GBs they can continue up to where we were - node->p->d.addCycles(&node->p->d, 0, node->eventDiff); + node->p->d.addCycles(&node->p->d, node->id, node->eventDiff); #ifndef NDEBUG node->phase = node->p->d.transferActive; #endif @@ -252,6 +252,12 @@ static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t valu mLockstepLock(&node->p->d); bool claimed = false; if (ATOMIC_CMPXCHG(node->p->masterClaimed, claimed, true)) { + if (node->id != 0) { + node->p->players[0]->id = 1; + node->p->players[1] = node->p->players[0]; + node->p->players[0] = node->p->players[1]; + node->id = 0; + } ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING); ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]); mTimingDeschedule(&driver->p->p->timing, &driver->p->event); diff --git a/src/gba/audio.c b/src/gba/audio.c index 00c20bd46..c4f989f63 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -44,6 +44,7 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) { GBAudioInit(&audio->psg, 0, nr52, GB_AUDIO_GBA); audio->psg.timing = &audio->p->timing; audio->psg.clockRate = GBA_ARM7TDMI_FREQUENCY; + audio->psg.frameEvent.context = audio; audio->samples = samples; // Guess too large; we hang producing extra samples if we guess too low blip_set_rates(audio->psg.left, GBA_ARM7TDMI_FREQUENCY, 96000); @@ -58,6 +59,8 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) { void GBAAudioReset(struct GBAAudio* audio) { GBAudioReset(&audio->psg); + mTimingDeschedule(&audio->p->timing, &audio->psg.frameEvent); + mTimingSchedule(&audio->p->timing, &audio->psg.frameEvent, 0); mTimingDeschedule(&audio->p->timing, &audio->sampleEvent); mTimingSchedule(&audio->p->timing, &audio->sampleEvent, 0); audio->chA.dmaSource = 1; @@ -77,11 +80,12 @@ void GBAAudioReset(struct GBAAudio* audio) { audio->chA.samples[i] = 0; audio->chB.samples[i] = 0; } - audio->sampleRate = 0x8000; audio->soundbias = 0x200; audio->volume = 0; audio->volumeChA = false; audio->volumeChB = false; + audio->lastSample = 0; + audio->sampleIndex = 0; audio->chARight = false; audio->chALeft = false; audio->chATimer = false; @@ -89,7 +93,7 @@ void GBAAudioReset(struct GBAAudio* audio) { audio->chBLeft = false; audio->chBTimer = false; audio->enable = false; - audio->sampleInterval = GBA_ARM7TDMI_FREQUENCY / audio->sampleRate; + audio->sampleInterval = GBA_ARM7TDMI_FREQUENCY / 0x8000; audio->psg.sampleInterval = audio->sampleInterval; blip_clear(audio->psg.left); @@ -141,56 +145,67 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA* } void GBAAudioWriteSOUND1CNT_LO(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR10(&audio->psg, value); } void GBAAudioWriteSOUND1CNT_HI(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR11(&audio->psg, value); GBAudioWriteNR12(&audio->psg, value >> 8); } void GBAAudioWriteSOUND1CNT_X(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR13(&audio->psg, value); GBAudioWriteNR14(&audio->psg, value >> 8); } void GBAAudioWriteSOUND2CNT_LO(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR21(&audio->psg, value); GBAudioWriteNR22(&audio->psg, value >> 8); } void GBAAudioWriteSOUND2CNT_HI(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR23(&audio->psg, value); GBAudioWriteNR24(&audio->psg, value >> 8); } void GBAAudioWriteSOUND3CNT_LO(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); audio->psg.ch3.size = GBAudioRegisterBankGetSize(value); audio->psg.ch3.bank = GBAudioRegisterBankGetBank(value); GBAudioWriteNR30(&audio->psg, value); } void GBAAudioWriteSOUND3CNT_HI(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR31(&audio->psg, value); audio->psg.ch3.volume = GBAudioRegisterBankVolumeGetVolumeGBA(value >> 8); } void GBAAudioWriteSOUND3CNT_X(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR33(&audio->psg, value); GBAudioWriteNR34(&audio->psg, value >> 8); } void GBAAudioWriteSOUND4CNT_LO(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR41(&audio->psg, value); GBAudioWriteNR42(&audio->psg, value >> 8); } void GBAAudioWriteSOUND4CNT_HI(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR43(&audio->psg, value); GBAudioWriteNR44(&audio->psg, value >> 8); } void GBAAudioWriteSOUNDCNT_LO(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); GBAudioWriteNR50(&audio->psg, value); GBAudioWriteNR51(&audio->psg, value >> 8); } @@ -328,17 +343,17 @@ static int _applyBias(struct GBAAudio* audio, int sample) { return ((sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) * audio->masterVolume * 3) >> 4; } -static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { - struct GBAAudio* audio = user; - int16_t samplesLeft[GBA_MAX_SAMPLES]; - int16_t samplesRight[GBA_MAX_SAMPLES]; - int32_t timestamp = mTimingCurrentTime(&audio->p->timing) - cyclesLate - SAMPLE_INTERVAL; +void GBAAudioSample(struct GBAAudio* audio, int32_t timestamp) { + timestamp -= audio->lastSample; + timestamp -= audio->sampleIndex * audio->sampleInterval; // TODO: This can break if the interval changes between samples + + int maxSample = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias); int sample; - for (sample = 0; sample * audio->sampleInterval < (int32_t) SAMPLE_INTERVAL; ++sample) { + for (sample = audio->sampleIndex; timestamp >= audio->sampleInterval && sample < maxSample; ++sample, timestamp -= audio->sampleInterval) { int16_t sampleLeft = 0; int16_t sampleRight = 0; int psgShift = 4 - audio->volume; - GBAudioRun(&audio->psg, timestamp + (sample + 1) * audio->sampleInterval, 0xF); + GBAudioRun(&audio->psg, sample * audio->sampleInterval + audio->lastSample, 0xF); GBAudioSamplePSG(&audio->psg, &sampleLeft, &sampleRight); sampleLeft >>= psgShift; sampleRight >>= psgShift; @@ -370,21 +385,34 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { sampleLeft = _applyBias(audio, sampleLeft); sampleRight = _applyBias(audio, sampleRight); - samplesLeft[sample] = sampleLeft; - samplesRight[sample] = sampleRight; + audio->currentSamples[sample].left = sampleLeft; + audio->currentSamples[sample].right = sampleRight; } - memset(audio->chA.samples, audio->chA.samples[sample - 1], sizeof(audio->chA.samples)); - memset(audio->chB.samples, audio->chB.samples[sample - 1], sizeof(audio->chB.samples)); + audio->sampleIndex = sample; + if (sample == maxSample) { + audio->lastSample += SAMPLE_INTERVAL; + audio->sampleIndex = 0; + } +} + +static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { + struct GBAAudio* audio = user; + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing) - cyclesLate); + + int samples = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias); + int sampleMask = 1 << GBARegisterSOUNDBIASGetResolution(audio->soundbias); + memset(audio->chA.samples, audio->chA.samples[samples - 1], sizeof(audio->chA.samples)); + memset(audio->chB.samples, audio->chB.samples[samples - 1], sizeof(audio->chB.samples)); mCoreSyncLockAudio(audio->p->sync); unsigned produced; int32_t sampleSumLeft = 0; int32_t sampleSumRight = 0; int i; - for (i = 0; i < sample; ++i) { - int16_t sampleLeft = samplesLeft[i]; - int16_t sampleRight = samplesRight[i]; + for (i = 0; i < samples; ++i) { + int16_t sampleLeft = audio->currentSamples[i].left; + int16_t sampleRight = audio->currentSamples[i].right; sampleSumLeft += sampleLeft; sampleSumRight += sampleRight; if ((size_t) blip_samples_avail(audio->psg.left) < audio->samples) { @@ -399,12 +427,14 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { audio->clock -= CLOCKS_PER_FRAME; } } - } - // TODO: Post all frames - if (audio->p->stream && audio->p->stream->postAudioFrame) { - sampleSumLeft /= sample; - sampleSumRight /= sample; - audio->p->stream->postAudioFrame(audio->p->stream, sampleSumLeft, sampleSumRight); + // TODO: Post all frames + if (audio->p->stream && audio->p->stream->postAudioFrame && (i & (sampleMask - 1)) == sampleMask - 1) { + sampleSumLeft /= sampleMask; + sampleSumRight /= sampleMask; + audio->p->stream->postAudioFrame(audio->p->stream, sampleSumLeft, sampleSumRight); + sampleSumLeft = 0; + sampleSumRight = 0; + } } produced = blip_samples_avail(audio->psg.left); bool wait = produced >= audio->samples; @@ -428,9 +458,15 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* memcpy(state->samples.chA, audio->chA.samples, sizeof(audio->chA.samples)); memcpy(state->samples.chB, audio->chB.samples, sizeof(audio->chB.samples)); + size_t i; + for (i = 0; i < GBA_MAX_SAMPLES; ++i) { + STORE_16(audio->currentSamples[i].left, 0, &state->currentSamples[i].left); + STORE_16(audio->currentSamples[i].right, 0, &state->currentSamples[i].right); + } + STORE_32(audio->lastSample, 0, &state->audio.lastSample); + int readA = audio->chA.fifoRead; int readB = audio->chB.fifoRead; - size_t i; for (i = 0; i < GBA_AUDIO_FIFO_SIZE; ++i) { STORE_32(audio->chA.fifo[readA], i << 2, state->audio.fifoA); STORE_32(audio->chB.fifo[readB], i << 2, state->audio.fifoB); @@ -464,6 +500,11 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* flags = GBASerializedAudioFlagsSetFIFOInternalSamplesA(flags, audio->chA.internalRemaining); flags = GBASerializedAudioFlagsSetFIFOInternalSamplesB(flags, audio->chB.internalRemaining); STORE_16(flags, 0, &state->audio.gbaFlags); + + GBASerializedAudioFlags2 flags2 = 0; + flags2 = GBASerializedAudioFlags2SetSampleIndex(flags2, audio->sampleIndex); + STORE_32(flags2, 0, &state->audio.gbaFlags2); + STORE_32(audio->sampleEvent.when - mTimingCurrentTime(&audio->p->timing), 0, &state->audio.nextSample); } @@ -475,9 +516,15 @@ void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState memcpy(audio->chA.samples, state->samples.chA, sizeof(audio->chA.samples)); memcpy(audio->chB.samples, state->samples.chB, sizeof(audio->chB.samples)); + size_t i; + for (i = 0; i < GBA_MAX_SAMPLES; ++i) { + LOAD_16(audio->currentSamples[i].left, 0, &state->currentSamples[i].left); + LOAD_16(audio->currentSamples[i].right, 0, &state->currentSamples[i].right); + } + LOAD_32(audio->lastSample, 0, &state->audio.lastSample); + int readA = 0; int readB = 0; - size_t i; for (i = 0; i < GBA_AUDIO_FIFO_SIZE; ++i) { LOAD_32(audio->chA.fifo[readA], i << 2, state->audio.fifoA); LOAD_32(audio->chB.fifo[readB], i << 2, state->audio.fifoB); @@ -494,8 +541,15 @@ void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState audio->chA.internalRemaining = GBASerializedAudioFlagsGetFIFOInternalSamplesA(flags); audio->chB.internalRemaining = GBASerializedAudioFlagsGetFIFOInternalSamplesB(flags); + GBASerializedAudioFlags2 flags2; + LOAD_32(flags2, 0, &state->audio.gbaFlags2); + audio->sampleIndex = GBASerializedAudioFlags2GetSampleIndex(flags2); + uint32_t when; LOAD_32(when, 0, &state->audio.nextSample); + if (state->versionMagic < 0x01000007) { + audio->lastSample = when - SAMPLE_INTERVAL; + } mTimingSchedule(&audio->p->timing, &audio->sampleEvent, when); } diff --git a/src/gba/extra/audio-mixer.c b/src/gba/extra/audio-mixer.c index fe9ed6901..503fdc8ad 100644 --- a/src/gba/extra/audio-mixer.c +++ b/src/gba/extra/audio-mixer.c @@ -125,7 +125,7 @@ static void _stepSample(struct GBAAudioMixer* mixer, struct GBAMP2kTrack* track) for (nSample = 0; nSample < updates; ++nSample) { int8_t sample = memory->load8(cpu, sampleBase + sampleI, 0); - struct GBAStereoSample stereo = { + struct mStereoSample stereo = { (sample * track->channel->leftVolume * track->channel->envelopeV) >> 9, (sample * track->channel->rightVolume * track->channel->envelopeV) >> 9 }; @@ -277,7 +277,7 @@ void _mp2kStep(struct GBAAudioMixer* mixer) { uint32_t interval = mixer->p->sampleInterval / OVERSAMPLE; int i; for (i = 0; i < OVERSAMPLE; ++i) { - struct GBAStereoSample sample = {0}; + struct mStereoSample sample = {0}; size_t track; for (track = 0; track < MP2K_MAX_SOUND_CHANNELS; ++track) { if (!mixer->activeTracks[track].channel->status) { diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index a0209a44a..59675120d 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -85,25 +85,37 @@ static const char* const _vertexShader = "}"; static const char* const _renderTile16 = + "#ifndef VRAM_MASK\n" + "#define VRAM_MASK\n" + "#endif\n" "int renderTile(int tile, int paletteId, ivec2 localCoord) {\n" " int address = charBase + tile * 16 + (localCoord.x >> 2) + (localCoord.y << 1);\n" - " int halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0).r;\n" + " int halfrow = texelFetch(vram, ivec2(address & 255, (address >> 8) VRAM_MASK), 0).r;\n" " int entry = (halfrow >> (4 * (localCoord.x & 3))) & 15;\n" " if (entry == 0) {\n" " discard;\n" " }\n" " return paletteId * 16 + entry;\n" + "}\n" + "int mask(int tile) {\n" + " return tile & 31;\n" "}"; static const char* const _renderTile256 = + "#ifndef VRAM_MASK\n" + "#define VRAM_MASK\n" + "#endif\n" "int renderTile(int tile, int paletteId, ivec2 localCoord) {\n" " int address = charBase + tile * 32 + (localCoord.x >> 1) + (localCoord.y << 2);\n" - " int halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0).r;\n" + " int halfrow = texelFetch(vram, ivec2(address & 255, (address >> 8) VRAM_MASK), 0).r;\n" " int entry = (halfrow >> (8 * (localCoord.x & 1))) & 255;\n" " if (entry == 0) {\n" " discard;\n" " }\n" " return entry;\n" + "}" + "int mask(int tile) {\n" + " return tile & 15;\n" "}"; static const struct GBAVideoGLUniform _uniformsMode0[] = { @@ -167,7 +179,7 @@ static const char* const _renderMode0 = " int tile = map & 1023;\n" " int paletteEntry = renderTile(tile, map >> 12, coord & 7);\n" " color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n" - "}"; + "}\n"; static const char* const _fetchTileOverflow = "int fetchTile(ivec2 coord) {\n" @@ -192,6 +204,7 @@ static const struct GBAVideoGLUniform _uniformsMode2[] = { { "vram", GBA_GL_BG_VRAM, }, { "palette", GBA_GL_BG_PALETTE, }, { "screenBase", GBA_GL_BG_SCREENBASE, }, + { "oldCharBase", GBA_GL_BG_OLDCHARBASE, }, { "charBase", GBA_GL_BG_CHARBASE, }, { "size", GBA_GL_BG_SIZE, }, { "offset", GBA_GL_BG_OFFSET, }, @@ -228,6 +241,7 @@ static const char* const _renderMode2 = "uniform isampler2D vram;\n" "uniform sampler2D palette;\n" "uniform int screenBase;\n" + "uniform ivec2 oldCharBase;\n" "uniform int charBase;\n" "uniform int size;\n" "uniform ivec4 transform[160];\n" @@ -244,7 +258,17 @@ static const char* const _renderMode2 = " int mapAddress = screenBase + (map >> 1);\n" " int twomaps = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0).r;\n" " int tile = (twomaps >> (8 * (map & 1))) & 255;\n" - " int address = charBase + tile * 32 + ((coord.x >> 9) & 3) + ((coord.y >> 6) & 0x1C);\n" + " int newCharBase = charBase;\n" + " if (newCharBase != oldCharBase.x) {\n" + " int y = int(texCoord.y);\n" + // If the charbase has changed (and the scale is greater than 1), we might still be drawing + // the tile associated with the pixel above us. If we're still on that tile, we want to use + // the charbase associated with it instead of the new one. Cf. https://mgba.io/i/1631 + " if (y == oldCharBase.y && transform[y - 1].w >> 11 == coord.y >> 11) {\n" + " newCharBase = oldCharBase.x;\n" + " }\n" + " }\n" + " int address = newCharBase + tile * 32 + ((coord.x >> 9) & 3) + ((coord.y >> 6) & 0x1C);\n" " int halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0).r;\n" " int entry = (halfrow >> (8 * ((coord.x >> 8) & 1))) & 255;\n" " if (entry == 0) {\n" @@ -415,6 +439,7 @@ static const struct GBAVideoGLUniform _uniformsObj[] = { { "objwin", GBA_GL_OBJ_OBJWIN, }, { "mosaic", GBA_GL_OBJ_MOSAIC, }, { "cyclesRemaining", GBA_GL_OBJ_CYCLES, }, + { "tile", GBA_GL_OBJ_TILE, }, { 0 } }; @@ -424,6 +449,7 @@ static const char* const _renderObj = "uniform isampler2D vram;\n" "uniform sampler2D palette;\n" "uniform int charBase;\n" + "uniform int tile;\n" "uniform int stride;\n" "uniform int localPalette;\n" "uniform ivec4 inflags;\n" @@ -437,6 +463,8 @@ static const char* const _renderObj = "OUT(2) out ivec4 window;\n" "int renderTile(int tile, int paletteId, ivec2 localCoord);\n" + "int mask(int);\n" + "#define VRAM_MASK & 191\n" "void main() {\n" " vec2 incoord = texCoord;\n" @@ -461,12 +489,12 @@ static const char* const _renderObj = " if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n" " discard;\n" " }\n" - " int paletteEntry = renderTile((coord.x >> 3) + (coord.y >> 3) * stride, localPalette, coord & 7);\n" + " int paletteEntry = renderTile(mask((coord.x >> 3) + tile) + (coord.y >> 3) * stride, localPalette, coord & 7);\n" " color = texelFetch(palette, ivec2(paletteEntry + 256, int(texCoord.y) + mosaic.w), 0);\n" " flags = inflags;\n" " gl_FragDepth = float(flags.x) / 16.;\n" " window = ivec4(objwin, 0);\n" - "}"; + "}\n"; static const struct GBAVideoGLUniform _uniformsObjPriority[] = { { "loc", GBA_GL_VS_LOC, }, @@ -996,42 +1024,34 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, case REG_BG0HOFS: value &= 0x01FF; glRenderer->bg[0].x = value; - dirty = false; break; case REG_BG0VOFS: value &= 0x01FF; glRenderer->bg[0].y = value; - dirty = false; break; case REG_BG1HOFS: value &= 0x01FF; glRenderer->bg[1].x = value; - dirty = false; break; case REG_BG1VOFS: value &= 0x01FF; glRenderer->bg[1].y = value; - dirty = false; break; case REG_BG2HOFS: value &= 0x01FF; glRenderer->bg[2].x = value; - dirty = false; break; case REG_BG2VOFS: value &= 0x01FF; glRenderer->bg[2].y = value; - dirty = false; break; case REG_BG3HOFS: value &= 0x01FF; glRenderer->bg[3].x = value; - dirty = false; break; case REG_BG3VOFS: value &= 0x01FF; glRenderer->bg[3].y = value; - dirty = false; break; case REG_BG2PA: glRenderer->bg[2].affine.dx = value; @@ -1330,6 +1350,7 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { if (_needsVramUpload(glRenderer, y) || glRenderer->oamDirty || glRenderer->regsDirty) { if (glRenderer->firstY >= 0) { _drawScanlines(glRenderer, y - 1); + glRenderer->firstY = y; glBindVertexArray(0); } } @@ -1610,6 +1631,7 @@ static void GBAVideoGLRendererUpdateDISPCNT(struct GBAVideoGLRenderer* renderer) static void GBAVideoGLRendererWriteBGCNT(struct GBAVideoGLBackground* bg, uint16_t value) { bg->priority = GBARegisterBGCNTGetPriority(value); + bg->oldCharBase = bg->charBase; bg->charBase = GBARegisterBGCNTGetCharBase(value) << 13; bg->mosaic = GBARegisterBGCNTGetMosaic(value); bg->multipalette = GBARegisterBGCNTGet256Color(value); @@ -1715,6 +1737,15 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB int align = GBAObjAttributesAIs256Color(sprite->a) && !GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt); unsigned charBase = (BASE_TILE >> 1) + (GBAObjAttributesCGetTile(sprite->c) & ~align) * 0x10; + unsigned tile = 0; + if (!GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt)) { + if (GBAObjAttributesAIs256Color(sprite->a)) { + tile = (charBase >> 5) & 0xF; + } else { + tile = (charBase >> 4) & 0x1F; + } + charBase &= ~0x1FF; + } int stride = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? (width >> 3) : (0x20 >> GBAObjAttributesAGet256Color(sprite->a)); int totalWidth = width; @@ -1750,6 +1781,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniform1i(uniforms[GBA_GL_OBJ_VRAM], 0); glUniform1i(uniforms[GBA_GL_OBJ_PALETTE], 1); glUniform1i(uniforms[GBA_GL_OBJ_CHARBASE], charBase); + glUniform1i(uniforms[GBA_GL_OBJ_TILE], tile); glUniform1i(uniforms[GBA_GL_OBJ_STRIDE], stride); glUniform1i(uniforms[GBA_GL_OBJ_LOCALPALETTE], GBAObjAttributesCGetPalette(sprite->c)); glUniform4i(uniforms[GBA_GL_OBJ_INFLAGS], GBAObjAttributesCGetPriority(sprite->c), @@ -1878,10 +1910,12 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, glBindVertexArray(shader->vao); _prepareTransform(renderer, background, uniforms, y); glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); + glUniform2i(uniforms[GBA_GL_BG_OLDCHARBASE], background->oldCharBase, renderer->firstY); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); + background->oldCharBase = background->charBase; } void GBAVideoGLRendererDrawBackgroundMode3(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 9c5416e72..c64d7c22b 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -15,7 +15,7 @@ #include MGBA_EXPORT const uint32_t GBASavestateMagic = 0x01000000; -MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000006; +MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000007; mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize"); diff --git a/src/gba/video.c b/src/gba/video.c index 51d03dc94..983976173 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -400,7 +400,12 @@ void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState break; } uint32_t when; - LOAD_32(when, 0, &state->video.nextEvent); + if (state->versionMagic < 0x01000007) { + // This field was moved in v7 + LOAD_32(when, 0, &state->audio.lastSample); + } else { + LOAD_32(when, 0, &state->video.nextEvent); + } mTimingSchedule(&video->p->timing, &video->event, when); LOAD_16(video->vcount, REG_VCOUNT, state->io); diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index 82f0c6029..f860bc4a1 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -88,7 +88,7 @@ static vita2d_texture* backdrop = 0; #define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 16) static struct mPSP2AudioContext { - struct GBAStereoSample buffer[PSP2_AUDIO_BUFFER_SIZE]; + struct mStereoSample buffer[PSP2_AUDIO_BUFFER_SIZE]; size_t writeOffset; size_t readOffset; size_t samples; @@ -255,7 +255,7 @@ static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* rig } ConditionWait(&audioContext.cond, &audioContext.mutex); } - struct GBAStereoSample* samples = &audioContext.buffer[audioContext.writeOffset]; + struct mStereoSample* samples = &audioContext.buffer[audioContext.writeOffset]; blip_read_samples(left, &samples[0].left, PSP2_SAMPLES, true); blip_read_samples(right, &samples[0].right, PSP2_SAMPLES, true); audioContext.samples += PSP2_SAMPLES; diff --git a/src/platform/qt/ApplicationUpdater.cpp b/src/platform/qt/ApplicationUpdater.cpp index c59413c26..ce752ecce 100644 --- a/src/platform/qt/ApplicationUpdater.cpp +++ b/src/platform/qt/ApplicationUpdater.cpp @@ -150,7 +150,13 @@ const char* ApplicationUpdater::platform() { return uninstallInfo.exists() ? "win32-installer" : "win32"; #endif #elif defined(Q_OS_MACOS) +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + // Modern macOS build + return "macos"; +#else + // Legacy "OS X" build return "osx"; +#endif #elif defined(Q_OS_LINUX) && defined(__x86_64__) return "appimage-x64"; #else diff --git a/src/platform/qt/AudioDevice.cpp b/src/platform/qt/AudioDevice.cpp index 9baf12712..74f863444 100644 --- a/src/platform/qt/AudioDevice.cpp +++ b/src/platform/qt/AudioDevice.cpp @@ -45,17 +45,17 @@ qint64 AudioDevice::readData(char* data, qint64 maxSize) { return 0; } - maxSize /= sizeof(GBAStereoSample); + maxSize /= sizeof(mStereoSample); mCoreSyncLockAudio(&m_context->impl->sync); int available = std::min({ blip_samples_avail(m_context->core->getAudioChannel(m_context->core, 0)), maxSize, std::numeric_limits::max() }); - blip_read_samples(m_context->core->getAudioChannel(m_context->core, 0), &reinterpret_cast(data)->left, available, true); - blip_read_samples(m_context->core->getAudioChannel(m_context->core, 1), &reinterpret_cast(data)->right, available, true); + blip_read_samples(m_context->core->getAudioChannel(m_context->core, 0), &reinterpret_cast(data)->left, available, true); + blip_read_samples(m_context->core->getAudioChannel(m_context->core, 1), &reinterpret_cast(data)->right, available, true); mCoreSyncConsumeAudio(&m_context->impl->sync); - return available * sizeof(GBAStereoSample); + return available * sizeof(mStereoSample); } qint64 AudioDevice::writeData(const char*, qint64) { diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 039e7b597..fd90b7ed3 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -404,7 +404,10 @@ if(QT_STATIC) find_package(Cups) find_package(${QT}PrintSupport) list(APPEND QT_LIBRARIES Cups ${QT}::PrintSupport ${QT}::QCocoaIntegrationPlugin ${QT}::CoreAudioPlugin ${QT}::AVFServicePlugin ${QT}::QCocoaPrinterSupportPlugin) - list(APPEND QT_LIBRARIES ${QT}AccessibilitySupport ${QT}CglSupport ${QT}ClipboardSupport ${QT}FontDatabaseSupport ${QT}GraphicsSupport ${QT}ThemeSupport) + list(APPEND QT_LIBRARIES ${QT}AccessibilitySupport ${QT}ClipboardSupport ${QT}FontDatabaseSupport ${QT}GraphicsSupport ${QT}ThemeSupport) + if(CMAKE_SYSTEM_VERSION VERSION_LESS "19.0") + list(APPEND QT_LIBRARIES ${QT}CglSupport) + endif() list(APPEND QT_LIBRARIES "-framework AVFoundation" "-framework CoreMedia" "-framework SystemConfiguration" "-framework Security") set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE}") elseif(UNIX) diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index 4fc4037e0..767cd277f 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -136,7 +136,7 @@ ConfigController::ConfigController(QObject* parent) m_subparsers[1].usage = "Frontend options:\n" " --ecard FILE Scan an e-Reader card in the first loaded game\n" - " Can be paassed multiple times for multiple cards\n" + " Can be passed multiple times for multiple cards\n" " --mb FILE Boot a multiboot image with FILE inserted into the ROM slot"; m_subparsers[1].parse = nullptr; m_subparsers[1].parseLong = [](struct mSubParser* parser, const char* option, const char* arg) { diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 598676739..694e97a12 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -56,10 +56,13 @@ uint qHash(const QSurfaceFormat& format, uint seed) { } void mGLWidget::initializeGL() { - m_vao.create(); - m_program.create(); + m_vao = std::make_unique(); + m_vao->create(); - m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, R"(#version 150 core + m_program = std::make_unique(); + m_program->create(); + + m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, R"(#version 150 core in vec4 position; out vec2 texCoord; void main() { @@ -67,7 +70,7 @@ void mGLWidget::initializeGL() { texCoord = (position.st + 1.0) * 0.5; })"); - m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, R"(#version 150 core + m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, R"(#version 150 core in vec2 texCoord; out vec4 color; uniform sampler2D tex; @@ -75,9 +78,11 @@ void mGLWidget::initializeGL() { color = vec4(texture(tex, texCoord).rgb, 1.0); })"); - m_program.link(); - m_program.setUniformValue("tex", 0); - m_positionLocation = m_program.attributeLocation("position"); + m_program->link(); + m_program->setUniformValue("tex", 0); + m_positionLocation = m_program->attributeLocation("position"); + + m_vaoDone = false; connect(&m_refresh, &QTimer::timeout, this, static_cast(&QWidget::update)); } @@ -85,28 +90,32 @@ void mGLWidget::initializeGL() { void mGLWidget::finalizeVAO() { QOpenGLFunctions_Baseline* fn = context()->versionFunctions(); fn->glGetError(); // Clear the error - m_vao.bind(); + m_vao->bind(); fn->glBindBuffer(GL_ARRAY_BUFFER, m_vbo); fn->glEnableVertexAttribArray(m_positionLocation); fn->glVertexAttribPointer(m_positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); - m_vao.release(); + m_vao->release(); if (fn->glGetError() == GL_NO_ERROR) { m_vaoDone = true; } } +void mGLWidget::reset() { + m_vaoDone = false; +} + void mGLWidget::paintGL() { if (!m_vaoDone) { finalizeVAO(); } QOpenGLFunctions_Baseline* fn = context()->versionFunctions(); - m_program.bind(); - m_vao.bind(); + m_program->bind(); + m_vao->bind(); fn->glBindTexture(GL_TEXTURE_2D, m_tex); fn->glDrawArrays(GL_TRIANGLE_FAN, 0, 4); fn->glBindTexture(GL_TEXTURE_2D, 0); - m_vao.release(); - m_program.release(); + m_vao->release(); + m_program->release(); // TODO: Better timing ++m_refreshResidue; @@ -205,9 +214,12 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { CoreController::Interrupter interrupter(controller); QMetaObject::invokeMethod(m_painter.get(), "start"); if (!m_gl) { - setUpdatesEnabled(false); + if (QGuiApplication::platformName() == "windows") { + setUpdatesEnabled(false); + } } else { show(); + m_gl->reset(); } } @@ -290,7 +302,7 @@ void DisplayGL::unpauseDrawing() { if (m_hasStarted) { m_isDrawing = true; QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection); - if (!m_gl) { + if (!m_gl && QGuiApplication::platformName() == "windows") { setUpdatesEnabled(false); } } diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 54256ed30..007cf6165 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -32,6 +32,7 @@ #include #include +#include #include "CoreController.h" #include "VideoProxy.h" @@ -52,6 +53,7 @@ public: void setTex(GLuint tex) { m_tex = tex; } void setVBO(GLuint vbo) { m_vbo = vbo; } void finalizeVAO(); + void reset(); protected: void initializeGL() override; @@ -62,8 +64,8 @@ private: GLuint m_vbo; bool m_vaoDone = false; - QOpenGLVertexArrayObject m_vao; - QOpenGLShaderProgram m_program; + std::unique_ptr m_vao; + std::unique_ptr m_program; GLuint m_positionLocation; QTimer m_refresh; diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index aa76ed523..3198a4c90 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -9,6 +9,7 @@ #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" #include "VFileDevice.h" +#include "utils.h" #include #include @@ -251,6 +252,10 @@ void LoadSaveState::focusInEvent(QFocusEvent*) { void LoadSaveState::paintEvent(QPaintEvent*) { QPainter painter(this); + + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); QRect full(QPoint(), size()); + painter.fillRect(full, Qt::black); + painter.drawPixmap(clampSize(m_dims, size(), m_lockAspectRatio, m_lockIntegerScaling), m_background); painter.fillRect(full, QColor(0, 0, 0, 128)); } diff --git a/src/platform/qt/LoadSaveState.h b/src/platform/qt/LoadSaveState.h index 5596c1b10..f74e5b24b 100644 --- a/src/platform/qt/LoadSaveState.h +++ b/src/platform/qt/LoadSaveState.h @@ -32,6 +32,10 @@ public: void setInputController(InputController* controller); void setMode(LoadSave mode); + void setBackground(const QPixmap& pixmap) { m_background = pixmap; } + void setDimensions(const QSize& dims) { m_dims = dims; } + void setLockIntegerScaling(bool lockIntegerScaling) { m_lockIntegerScaling = lockIntegerScaling; } + void setLockAspectRatio(bool lockApsectRatio) { m_lockAspectRatio = lockApsectRatio; } signals: void closed(); @@ -54,6 +58,11 @@ private: int m_currentFocus; QPixmap m_currentImage; + QPixmap m_background; + + QSize m_dims; + bool m_lockAspectRatio; + bool m_lockIntegerScaling; }; } diff --git a/src/platform/qt/ReportView.cpp b/src/platform/qt/ReportView.cpp index edfddd21c..b38483e7f 100644 --- a/src/platform/qt/ReportView.cpp +++ b/src/platform/qt/ReportView.cpp @@ -61,6 +61,10 @@ #include #endif +#ifdef USE_LUA +#include +#endif + #ifdef USE_LZMA #include <7zVersion.h> #endif @@ -168,6 +172,11 @@ void ReportView::generateReport() { #else swReport << QString("libLZMA not linked"); #endif +#ifdef USE_LUA + swReport << QString("Lua version: %1").arg(QLatin1String(LUA_RELEASE)); +#else + swReport << QString("Lua not linked"); +#endif #ifdef USE_MINIZIP swReport << QString("minizip linked"); #else diff --git a/src/platform/qt/SensorView.ui b/src/platform/qt/SensorView.ui index d75b5329d..7d65ba52c 100644 --- a/src/platform/qt/SensorView.ui +++ b/src/platform/qt/SensorView.ui @@ -284,10 +284,10 @@ false - -2147483647 + -1073741823 - 2147483647 + 1073741823 0 diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 199e12787..03c494279 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #ifdef USE_SQLITE3 @@ -58,7 +57,6 @@ #include "TileView.h" #include "VideoProxy.h" #include "VideoView.h" -#include "utils.h" #ifdef USE_DISCORD_RPC #include "DiscordCoordinator.h" @@ -137,14 +135,12 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi m_libraryView = new LibraryController(nullptr, ConfigController::configDir() + "/library.sqlite3", m_config); ConfigOption* showLibrary = m_config->addOption("showLibrary"); showLibrary->connect([this](const QVariant& value) { - if (value.toBool()) { - if (m_controller) { - m_screenWidget->layout()->addWidget(m_libraryView); - } else { + if (!m_controller) { + if (value.toBool()) { attachWidget(m_libraryView); + } else { + attachWidget(m_screenWidget); } - } else { - detachWidget(m_libraryView); } }, this); m_config->updateOption("showLibrary"); @@ -174,7 +170,6 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi resizeFrame(QSize(GB_VIDEO_HORIZONTAL_PIXELS * i, GB_VIDEO_VERTICAL_PIXELS * i)); #endif setLogo(); - setCentralWidget(m_screenWidget); connect(this, &Window::shutdown, m_logView, &QWidget::hide); connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS); @@ -947,7 +942,6 @@ void Window::gameStarted() { } #endif QSize size = m_controller->screenDimensions(); - m_screenWidget->setDimensions(size.width(), size.height()); m_config->updateOption("lockIntegerScaling"); m_config->updateOption("lockAspectRatio"); m_config->updateOption("interframeBlending"); @@ -1043,7 +1037,6 @@ void Window::gameStopped() { m_audioProcessor.reset(); } m_display->stopDrawing(); - detachWidget(m_display.get()); setLogo(); if (m_display) { #ifdef M_CORE_GB @@ -1054,6 +1047,7 @@ void Window::gameStopped() { } m_controller.reset(); + detachWidget(); updateTitle(); if (m_pendingClose) { @@ -1105,7 +1099,7 @@ void Window::unimplementedBiosCall(int) { void Window::reloadDisplayDriver() { if (m_controller) { m_display->stopDrawing(); - detachWidget(m_display.get()); + detachWidget(); } m_display = std::unique_ptr(Display::create(this)); if (!m_display) { @@ -1120,7 +1114,7 @@ void Window::reloadDisplayDriver() { #endif connect(m_display.get(), &QGBA::Display::hideCursor, [this]() { - if (static_cast(m_screenWidget->layout())->currentWidget() == m_display.get()) { + if (centralWidget() == m_display.get()) { m_screenWidget->setCursor(Qt::BlankCursor); } }); @@ -1281,15 +1275,13 @@ void Window::openStateWindow(LoadSave ls) { m_stateWindow = new LoadSaveState(m_controller); connect(this, &Window::shutdown, m_stateWindow, &QWidget::close); connect(m_stateWindow, &LoadSaveState::closed, [this]() { - detachWidget(m_stateWindow); - static_cast(m_screenWidget->layout())->setCurrentWidget(m_display.get()); + attachWidget(m_display.get()); m_stateWindow = nullptr; QMetaObject::invokeMethod(this, "setFocus", Qt::QueuedConnection); }); if (!wasPaused) { m_controller->setPaused(true); connect(m_stateWindow, &LoadSaveState::closed, [this]() { - m_screenWidget->filter(m_config->getOption("resampleVideo").toInt()); if (m_controller) { m_controller->setPaused(false); } @@ -1298,6 +1290,10 @@ void Window::openStateWindow(LoadSave ls) { m_stateWindow->setAttribute(Qt::WA_DeleteOnClose); m_stateWindow->setMode(ls); + m_stateWindow->setDimensions(m_controller->screenDimensions()); + m_config->updateOption("lockAspectRatio"); + m_config->updateOption("lockIntegerScaling"); + QImage still(m_controller->getPixels()); if (still.format() != QImage::Format_RGB888) { still = still.convertToFormat(QImage::Format_RGB888); @@ -1315,8 +1311,7 @@ void Window::openStateWindow(LoadSave ls) { QPixmap pixmap; pixmap.convertFromImage(output); - m_screenWidget->setPixmap(pixmap); - m_screenWidget->filter(true); + m_stateWindow->setBackground(pixmap); #ifndef Q_OS_MAC menuBar()->show(); @@ -1629,8 +1624,8 @@ void Window::setupMenu(QMenuBar* menubar) { if (m_display) { m_display->lockAspectRatio(value.toBool()); } - if (m_controller) { - m_screenWidget->setLockAspectRatio(value.toBool()); + if (m_stateWindow) { + m_stateWindow->setLockAspectRatio(value.toBool()); } }, this); m_config->updateOption("lockAspectRatio"); @@ -1641,8 +1636,8 @@ void Window::setupMenu(QMenuBar* menubar) { if (m_display) { m_display->lockIntegerScaling(value.toBool()); } - if (m_controller) { - m_screenWidget->setLockIntegerScaling(value.toBool()); + if (m_stateWindow) { + m_stateWindow->setLockIntegerScaling(value.toBool()); } }, this); m_config->updateOption("lockIntegerScaling"); @@ -1662,9 +1657,6 @@ void Window::setupMenu(QMenuBar* menubar) { if (m_display) { m_display->filter(value.toBool()); } - if (m_controller) { - m_screenWidget->filter(value.toBool()); - } }, this); m_config->updateOption("resampleVideo"); @@ -1934,13 +1926,12 @@ void Window::setupOptions() { } void Window::attachWidget(QWidget* widget) { - m_screenWidget->layout()->addWidget(widget); - m_screenWidget->unsetCursor(); - static_cast(m_screenWidget->layout())->setCurrentWidget(widget); + takeCentralWidget(); + setCentralWidget(widget); } -void Window::detachWidget(QWidget* widget) { - m_screenWidget->layout()->removeWidget(widget); +void Window::detachWidget() { + m_config->updateOption("showLibrary"); } void Window::appendMRU(const QString& fname) { @@ -2036,7 +2027,7 @@ void Window::focusCheck() { } void Window::updateFrame() { - if (static_cast(m_screenWidget->layout())->currentWidget() != m_display.get()) { + if (!m_controller) { return; } QPixmap pixmap; @@ -2210,17 +2201,13 @@ void Window::updateMute() { void Window::setLogo() { m_screenWidget->setPixmap(m_logo); - m_screenWidget->setCenteredAspectRatio(m_logo.width(), m_logo.height()); - m_screenWidget->setLockIntegerScaling(false); - m_screenWidget->filter(true); + m_screenWidget->setDimensions(m_logo.width(), m_logo.height()); m_screenWidget->unsetCursor(); } WindowBackground::WindowBackground(QWidget* parent) : QWidget(parent) { - setLayout(new QStackedLayout()); - layout()->setContentsMargins(0, 0, 0, 0); } void WindowBackground::setPixmap(const QPixmap& pmap) { @@ -2238,7 +2225,6 @@ QSize WindowBackground::sizeHint() const { void WindowBackground::setCenteredAspectRatio(int width, int height) { m_centered = true; - m_lockAspectRatio = true; setDimensions(width, height); } @@ -2247,25 +2233,12 @@ void WindowBackground::setDimensions(int width, int height) { m_aspectHeight = height; } -void WindowBackground::setLockIntegerScaling(bool lock) { - m_lockIntegerScaling = lock; -} - -void WindowBackground::setLockAspectRatio(bool lock) { - m_centered = false; - m_lockAspectRatio = lock; -} - -void WindowBackground::filter(bool filter) { - m_filter = filter; -} - void WindowBackground::paintEvent(QPaintEvent* event) { QWidget::paintEvent(event); const QPixmap& logo = pixmap(); QPainter painter(this); - painter.setRenderHint(QPainter::SmoothPixmapTransform, m_filter); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); painter.fillRect(QRect(QPoint(), size()), Qt::black); - QRect full(clampSize(QSize(m_aspectWidth, m_aspectHeight), size(), m_lockAspectRatio, m_lockIntegerScaling, m_centered)); + QRect full(clampSize(QSize(m_aspectWidth, m_aspectHeight), size(), true, false, m_centered)); painter.drawPixmap(full, logo); } diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 37faa8fec..fcaf22ba5 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -167,7 +167,7 @@ private: void openStateWindow(LoadSave); void attachWidget(QWidget* widget); - void detachWidget(QWidget* widget); + void detachWidget(); void appendMRU(const QString& fname); void clearMRU(); @@ -276,7 +276,6 @@ public: void setCenteredAspectRatio(int width, int height); void setLockIntegerScaling(bool lock); void setLockAspectRatio(bool lock); - void filter(bool filter); const QPixmap& pixmap() const { return m_pixmap; } @@ -289,9 +288,6 @@ private: bool m_centered; int m_aspectWidth; int m_aspectHeight; - bool m_lockAspectRatio; - bool m_lockIntegerScaling; - bool m_filter; }; } diff --git a/src/platform/qt/input/InputController.cpp b/src/platform/qt/input/InputController.cpp index 27d45b371..3d588f3fd 100644 --- a/src/platform/qt/input/InputController.cpp +++ b/src/platform/qt/input/InputController.cpp @@ -461,6 +461,11 @@ void InputController::registerGyroAxisX(int axis) { #ifdef BUILD_SDL if (m_playerAttached) { m_sdlPlayer.rotation.gyroX = axis; + if (m_sdlPlayer.rotation.gyroY == axis) { + m_sdlPlayer.rotation.gyroZ = axis; + } else { + m_sdlPlayer.rotation.gyroZ = -1; + } } #endif } @@ -469,6 +474,11 @@ void InputController::registerGyroAxisY(int axis) { #ifdef BUILD_SDL if (m_playerAttached) { m_sdlPlayer.rotation.gyroY = axis; + if (m_sdlPlayer.rotation.gyroX == axis) { + m_sdlPlayer.rotation.gyroZ = axis; + } else { + m_sdlPlayer.rotation.gyroZ = -1; + } } #endif } diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index f394f8e67..1d4b262f3 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -56,6 +56,9 @@ elseif(APPLE) if(NOT CMAKE_SYSTEM_VERSION VERSION_LESS "17.0") # Darwin 17.x is macOS 10.13 list(APPEND SDL_LIBRARY "-framework Metal") endif() + if(NOT CMAKE_SYSTEM_VERSION VERSION_LESS "19.0") # Darwin 19.x is macOS 10.15 + list(APPEND SDL_LIBRARY "-framework GameController" "-framework CoreHaptics") + endif() endif() if(NOT SDLMAIN_LIBRARY) diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 0b353d2d9..0d1098f52 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -199,6 +199,7 @@ bool mSDLAttachPlayer(struct mSDLEvents* events, struct mSDLPlayer* player) { player->rotation.gyroSensitivity = 2.2e9f; player->rotation.gyroX = 0; player->rotation.gyroY = 1; + player->rotation.gyroZ = -1; player->rotation.zDelta = 0; CircleBufferInit(&player->rotation.zHistory, sizeof(float) * GYRO_STEPS); player->rotation.p = player; @@ -324,6 +325,13 @@ void mSDLPlayerLoadConfig(struct mSDLPlayer* context, const struct Configuration context->rotation.gyroY = axis; } } + value = mInputGetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroAxisZ", name); + if (value) { + axis = strtol(value, &end, 0); + if (axis >= 0 && axis < numAxes && end && !*end) { + context->rotation.gyroZ = axis; + } + } value = mInputGetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroSensitivity", name); if (value) { float sensitivity = strtof_u(value, &end); @@ -354,6 +362,8 @@ void mSDLPlayerSaveConfig(const struct mSDLPlayer* context, struct Configuration mInputSetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroAxisX", value, name); snprintf(value, sizeof(value), "%i", context->rotation.gyroY); mInputSetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroAxisY", value, name); + snprintf(value, sizeof(value), "%i", context->rotation.gyroZ); + mInputSetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroAxisZ", value, name); snprintf(value, sizeof(value), "%g", context->rotation.gyroSensitivity); mInputSetCustomValue(config, "gba", SDL_BINDING_BUTTON, "gyroSensitivity", value, name); } @@ -790,6 +800,10 @@ static void _mSDLRotationSample(struct mRotationSource* source) { } } #endif + if (rotation->gyroZ >= 0) { + rotation->zDelta = SDL_JoystickGetAxis(rotation->p->joystick->joystick, rotation->gyroZ) / 1.e5f; + return; + } int x = SDL_JoystickGetAxis(rotation->p->joystick->joystick, rotation->gyroX); int y = SDL_JoystickGetAxis(rotation->p->joystick->joystick, rotation->gyroY); diff --git a/src/platform/sdl/sdl-events.h b/src/platform/sdl/sdl-events.h index 1763865e2..8bdbd7c0b 100644 --- a/src/platform/sdl/sdl-events.h +++ b/src/platform/sdl/sdl-events.h @@ -95,6 +95,7 @@ struct mSDLPlayer { // Gyro int gyroX; int gyroY; + int gyroZ; float gyroSensitivity; struct CircleBuffer zHistory; int oldX; diff --git a/src/platform/switch/main.c b/src/platform/switch/main.c index 0a40d3a90..25b6f6eca 100644 --- a/src/platform/switch/main.c +++ b/src/platform/switch/main.c @@ -115,7 +115,7 @@ static float gyroZ = 0; static float tiltX = 0; static float tiltY = 0; -static struct GBAStereoSample audioBuffer[N_BUFFERS][BUFFER_SIZE / 4] __attribute__((__aligned__(0x1000))); +static struct mStereoSample audioBuffer[N_BUFFERS][BUFFER_SIZE / 4] __attribute__((__aligned__(0x1000))); static enum ScreenMode { SM_PA, @@ -584,7 +584,7 @@ static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* rig blip_clear(right); return; } - struct GBAStereoSample* samples = audioBuffer[audioBufferActive]; + struct mStereoSample* samples = audioBuffer[audioBufferActive]; blip_read_samples(left, &samples[0].left, SAMPLES, true); blip_read_samples(right, &samples[0].right, SAMPLES, true); audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]); diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index 8b3ed8226..36bb9abfc 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -141,7 +141,7 @@ static void* framebuffer[2] = { 0, 0 }; static int whichFb = 0; static struct AudioBuffer { - struct GBAStereoSample samples[SAMPLES] __attribute__((__aligned__(32))); + struct mStereoSample samples[SAMPLES] __attribute__((__aligned__(32))); volatile size_t size; } audioBuffer[BUFFERS] = {0}; static volatile int currentAudioBuffer = 0; @@ -685,8 +685,8 @@ static void _audioDMA(void) { if (buffer->size != SAMPLES) { return; } - DCFlushRange(buffer->samples, SAMPLES * sizeof(struct GBAStereoSample)); - AUDIO_InitDMA((u32) buffer->samples, SAMPLES * sizeof(struct GBAStereoSample)); + DCFlushRange(buffer->samples, SAMPLES * sizeof(struct mStereoSample)); + AUDIO_InitDMA((u32) buffer->samples, SAMPLES * sizeof(struct mStereoSample)); buffer->size = 0; currentAudioBuffer = (currentAudioBuffer + 1) % BUFFERS; } diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 2770e8898..f5adfcdd5 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -11,6 +11,10 @@ #include #include +#ifdef _WIN32 +#include +#endif + #define MAX_KEY_SIZE 128 static struct mScriptEngineContext* _luaCreate(struct mScriptEngine2*, struct mScriptContext*); @@ -45,10 +49,20 @@ static int _luaPairsTable(lua_State* lua); static int _luaGetList(lua_State* lua); static int _luaLenList(lua_State* lua); +static int _luaRequireShim(lua_State* lua); + #if LUA_VERSION_NUM < 503 #define lua_pushinteger lua_pushnumber #endif +#ifndef LUA_OK +#define LUA_OK 0 +#endif + +#if LUA_VERSION_NUM < 502 +#define luaL_traceback(L, M, S, level) lua_pushstring(L, S) +#endif + const struct mScriptType mSTLuaFunc = { .base = mSCRIPT_TYPE_FUNCTION, .size = 0, @@ -74,6 +88,7 @@ struct mScriptEngineContextLua { struct mScriptEngineContext d; lua_State* lua; int func; + int require; char* lastError; }; @@ -159,6 +174,9 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS #endif lua_pop(luaContext->lua, 1); + lua_getglobal(luaContext->lua, "require"); + luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); + return &luaContext->d; } @@ -171,6 +189,9 @@ void _luaDestroy(struct mScriptEngineContext* ctx) { if (luaContext->func > 0) { luaL_unref(luaContext->lua, LUA_REGISTRYINDEX, luaContext->func); } + if (luaContext->require > 0) { + luaL_unref(luaContext->lua, LUA_REGISTRYINDEX, luaContext->require); + } lua_close(luaContext->lua); free(luaContext); } @@ -326,7 +347,7 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool } return _luaCoerceFunction(luaContext); case LUA_TTABLE: - // This function pops the value internally via luaL_ref + // This function pops the value internally if (!pop) { break; } @@ -414,7 +435,8 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v } else { mScriptValueWrap(value, newValue); } - luaL_setmetatable(luaContext->lua, "mSTList"); + lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTList"); + lua_setmetatable(luaContext->lua, -2); break; case mSCRIPT_TYPE_TABLE: newValue = lua_newuserdata(luaContext->lua, sizeof(*newValue)); @@ -423,7 +445,8 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v } else { mScriptValueWrap(value, newValue); } - luaL_setmetatable(luaContext->lua, "mSTTable"); + lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTTable"); + lua_setmetatable(luaContext->lua, -2); break; case mSCRIPT_TYPE_FUNCTION: newValue = lua_newuserdata(luaContext->lua, sizeof(*newValue)); @@ -440,7 +463,8 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v } else { mScriptValueWrap(value, newValue); } - luaL_setmetatable(luaContext->lua, "mSTStruct"); + lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTStruct"); + lua_setmetatable(luaContext->lua, -2); break; default: ok = false; @@ -476,7 +500,8 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi free(luaContext->lastError); luaContext->lastError = NULL; } - char name[80]; + char name[PATH_MAX + 1]; + char dirname[PATH_MAX] = {0}; if (filename) { if (*filename == '*') { snprintf(name, sizeof(name), "=%s", filename + 1); @@ -484,23 +509,34 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi const char* lastSlash = strrchr(filename, '/'); const char* lastBackslash = strrchr(filename, '\\'); if (lastSlash && lastBackslash) { - if (lastSlash > lastBackslash) { - filename = lastSlash + 1; - } else { - filename = lastBackslash + 1; + if (lastSlash < lastBackslash) { + lastSlash = lastBackslash; } - } else if (lastSlash) { - filename = lastSlash + 1; } else if (lastBackslash) { - filename = lastBackslash + 1; + lastSlash = lastBackslash; + } + if (lastSlash) { + strncpy(dirname, filename, lastSlash - filename); } snprintf(name, sizeof(name), "@%s", filename); } filename = name; } +#if LUA_VERSION_NUM >= 502 int ret = lua_load(luaContext->lua, _reader, &data, filename, "t"); +#else + int ret = lua_load(luaContext->lua, _reader, &data, filename); +#endif switch (ret) { case LUA_OK: + if (dirname[0]) { + lua_getupvalue(luaContext->lua, -1, 1); + lua_pushliteral(luaContext->lua, "require"); + lua_pushstring(luaContext->lua, dirname); + lua_pushcclosure(luaContext->lua, _luaRequireShim, 1); + lua_rawset(luaContext->lua, -3); + lua_pop(luaContext->lua, 1); + } luaContext->func = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); return true; case LUA_ERRSYNTAX: @@ -515,6 +551,7 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi bool _luaRun(struct mScriptEngineContext* context) { struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) context; + lua_rawgeti(luaContext->lua, LUA_REGISTRYINDEX, luaContext->func); return _luaInvoke(luaContext, NULL); } @@ -640,8 +677,8 @@ void _luaDeref(struct mScriptValue* value) { static struct mScriptEngineContextLua* _luaGetContext(lua_State* lua) { lua_pushliteral(lua, "mCtx"); - int type = lua_rawget(lua, LUA_REGISTRYINDEX); - if (type != LUA_TLIGHTUSERDATA) { + lua_rawget(lua, LUA_REGISTRYINDEX); + if (lua_type(lua, -1) != LUA_TLIGHTUSERDATA) { lua_pop(lua, 1); lua_pushliteral(lua, "Function called from invalid context"); lua_error(lua); @@ -955,3 +992,68 @@ static int _luaLenList(lua_State* lua) { lua_pushinteger(lua, mScriptListSize(list)); return 1; } + +static int _luaRequireShim(lua_State* lua) { + struct mScriptEngineContextLua* luaContext = _luaGetContext(lua); + + int oldtop = lua_gettop(luaContext->lua); + const char* path = lua_tostring(lua, lua_upvalueindex(1)); + + lua_getglobal(luaContext->lua, "package"); + + lua_pushliteral(luaContext->lua, "path"); + lua_pushstring(luaContext->lua, path); + lua_pushliteral(luaContext->lua, "/?.lua;"); + lua_pushstring(luaContext->lua, path); + lua_pushliteral(luaContext->lua, "/?/init.lua;"); + lua_pushliteral(luaContext->lua, "path"); + lua_gettable(luaContext->lua, -7); + char* oldpath = strdup(lua_tostring(luaContext->lua, -1)); + lua_concat(luaContext->lua, 5); + lua_settable(luaContext->lua, -3); + +#ifdef _WIN32 +#define DLL "dll" +#elif defined(__APPLE__) +#define DLL "dylib" +#else +#define DLL "so" +#endif + lua_pushliteral(luaContext->lua, "cpath"); + lua_pushstring(luaContext->lua, path); + lua_pushliteral(luaContext->lua, "/?." DLL ";"); + lua_pushstring(luaContext->lua, path); + lua_pushliteral(luaContext->lua, "/?/init." DLL ";"); + lua_pushliteral(luaContext->lua, "cpath"); + lua_gettable(luaContext->lua, -7); + char* oldcpath = strdup(lua_tostring(luaContext->lua, -1)); + lua_concat(luaContext->lua, 5); + lua_settable(luaContext->lua, -3); + + lua_pop(luaContext->lua, 1); + + lua_rawgeti(luaContext->lua, LUA_REGISTRYINDEX, luaContext->require); + lua_insert(luaContext->lua, -2); + int ret = lua_pcall(luaContext->lua, 1, LUA_MULTRET, 0); + + lua_getglobal(luaContext->lua, "package"); + + lua_pushliteral(luaContext->lua, "path"); + lua_pushstring(luaContext->lua, oldpath); + lua_settable(luaContext->lua, -3); + + lua_pushliteral(luaContext->lua, "cpath"); + lua_pushstring(luaContext->lua, oldcpath); + lua_settable(luaContext->lua, -3); + + lua_pop(luaContext->lua, 1); + + free(oldpath); + free(oldcpath); + if (ret) { + lua_error(luaContext->lua); + } + + int newtop = lua_gettop(luaContext->lua); + return newtop - oldtop + 1; +}