Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2022-06-27 21:28:06 -07:00
commit fb2c19e9ce
33 changed files with 450 additions and 176 deletions

View File

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

View File

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

View File

@ -11,6 +11,7 @@
CXX_GUARD_START
#include <mgba/core/cpu.h>
#include <mgba/core/interface.h>
#include <mgba/core/log.h>
#include <mgba/internal/gb/audio.h>
#include <mgba-util/circle-buffer.h>
@ -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);

View File

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

View File

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

View File

@ -11,6 +11,9 @@
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/serialize.h>
#include <mgba/internal/gb/io.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/audio.h>
#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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@
#include <fcntl.h>
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");

View File

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

View File

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

View File

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

View File

@ -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<qint64>({
blip_samples_avail(m_context->core->getAudioChannel(m_context->core, 0)),
maxSize,
std::numeric_limits<int>::max()
});
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 0), &reinterpret_cast<GBAStereoSample*>(data)->left, available, true);
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 1), &reinterpret_cast<GBAStereoSample*>(data)->right, available, true);
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 0), &reinterpret_cast<mStereoSample*>(data)->left, available, true);
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 1), &reinterpret_cast<mStereoSample*>(data)->right, available, true);
mCoreSyncConsumeAudio(&m_context->impl->sync);
return available * sizeof(GBAStereoSample);
return available * sizeof(mStereoSample);
}
qint64 AudioDevice::writeData(const char*, qint64) {

View File

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

View File

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

View File

@ -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<QOpenGLVertexArrayObject>();
m_vao->create();
m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, R"(#version 150 core
m_program = std::make_unique<QOpenGLShaderProgram>();
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<void (QWidget::*)()>(&QWidget::update));
}
@ -85,28 +90,32 @@ void mGLWidget::initializeGL() {
void mGLWidget::finalizeVAO() {
QOpenGLFunctions_Baseline* fn = context()->versionFunctions<QOpenGLFunctions_Baseline>();
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<QOpenGLFunctions_Baseline>();
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<CoreController> 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);
}
}

View File

@ -32,6 +32,7 @@
#include <QTimer>
#include <array>
#include <memory>
#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<QOpenGLVertexArrayObject> m_vao;
std::unique_ptr<QOpenGLShaderProgram> m_program;
GLuint m_positionLocation;
QTimer m_refresh;

View File

@ -9,6 +9,7 @@
#include "GamepadAxisEvent.h"
#include "GamepadButtonEvent.h"
#include "VFileDevice.h"
#include "utils.h"
#include <QAction>
#include <QDateTime>
@ -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));
}

View File

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

View File

@ -61,6 +61,10 @@
#include <zip.h>
#endif
#ifdef USE_LUA
#include <lua.h>
#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

View File

@ -284,10 +284,10 @@
<bool>false</bool>
</property>
<property name="minimum">
<number>-2147483647</number>
<number>-1073741823</number>
</property>
<property name="maximum">
<number>2147483647</number>
<number>1073741823</number>
</property>
<property name="value">
<number>0</number>

View File

@ -12,7 +12,6 @@
#include <QMimeData>
#include <QPainter>
#include <QScreen>
#include <QStackedLayout>
#include <QWindow>
#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<QGBA::Display>(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<QStackedLayout*>(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<QStackedLayout*>(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<QStackedLayout*>(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<QStackedLayout*>(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);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -95,6 +95,7 @@ struct mSDLPlayer {
// Gyro
int gyroX;
int gyroY;
int gyroZ;
float gyroSensitivity;
struct CircleBuffer zHistory;
int oldX;

View File

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

View File

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

View File

@ -11,6 +11,10 @@
#include <lualib.h>
#include <lauxlib.h>
#ifdef _WIN32
#include <windows.h>
#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;
}