Merge branch 'master' into medusa

This commit is contained in:
Vicki Pfau 2022-06-05 04:49:15 -07:00
commit d693a90023
33 changed files with 827 additions and 375 deletions

View File

@ -62,12 +62,15 @@ Emulation fixes:
- GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378)
- GB Audio: Fix channel 1/2 reseting edge cases (fixes mgba.io/i/1925)
- GB Audio: Properly apply per-model audio differences
- GB Audio: Revamp channel rendering
- GB Memory: Add cursory cartridge open bus emulation (fixes mgba.io/i/2032)
- GB Serialize: Fix loading MBC1 states that affect bank 0 (fixes mgba.io/i/2402)
- 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)
- GBA: Fix booting multiboot ROMs with no JOY entrypoint
- GBA Audio: Adjust PSG sampling rate with SOUNDBIAS
- GBA Audio: Sample FIFOs at SOUNDBIAS-set frequency
- GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059)
- GBA BIOS: Initial HLE timing estimation of UnLz77 functions (fixes mgba.io/i/2141)
- GBA DMA: Fix DMA source direction bits being cleared (fixes mgba.io/i/2410)

View File

@ -974,14 +974,17 @@ if(BUILD_OPENEMU)
install(TARGETS ${BINARY_NAME}-openemu LIBRARY DESTINATION ${OE_LIBDIR} COMPONENT ${BINARY_NAME}.oecoreplugin NAMELINK_SKIP)
endif()
if(BUILD_QT AND (WIN32 OR APPLE))
if(BUILD_QT AND (WIN32 OR APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux"))
set(BUILD_UPDATER ON)
endif()
if(BUILD_UPDATER)
add_executable(updater-stub WIN32 ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater-main.c)
target_link_libraries(updater-stub ${OS_LIB} ${PLATFORM_LIBRARY} ${BINARY_NAME})
set_target_properties(updater-stub PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FUNCTION_DEFINES}")
add_executable(updater-stub WIN32 ${CORE_VFS_SRC} ${OS_SRC} ${UTIL_SRC} ${THIRD_PARTY_SRC}
${CMAKE_CURRENT_SOURCE_DIR}/src/core/config.c
${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater.c
${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater-main.c)
target_link_libraries(updater-stub ${OS_LIB} ${PLATFORM_LIBRARY})
set_target_properties(updater-stub PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FUNCTION_DEFINES};BUILD_STATIC")
if(MSVC)
set_target_properties(updater-stub PROPERTIES LINK_FLAGS /ENTRY:mainCRTStartup)
else()

View File

@ -87,7 +87,6 @@ struct GBAudioSquareControl {
int frequency;
int length;
bool stop;
int hi;
};
struct GBAudioSweep {
@ -104,6 +103,8 @@ struct GBAudioSquareChannel {
struct GBAudioSweep sweep;
struct GBAudioEnvelope envelope;
struct GBAudioSquareControl control;
int32_t lastUpdate;
uint8_t index;
int8_t sample;
};
@ -112,6 +113,7 @@ struct GBAudioWaveChannel {
bool bank;
bool enable;
int8_t sample;
unsigned length;
int volume;
@ -124,7 +126,7 @@ struct GBAudioWaveChannel {
uint32_t wavedata32[8];
uint8_t wavedata8[16];
};
int8_t sample;
int32_t nextUpdate;
};
struct GBAudioNoiseChannel {
@ -194,11 +196,6 @@ struct GBAudio {
enum GBAudioStyle style;
struct mTimingEvent frameEvent;
struct mTimingEvent ch1Event;
struct mTimingEvent ch2Event;
struct mTimingEvent ch3Event;
struct mTimingEvent ch3Fade;
struct mTimingEvent ch4Event;
struct mTimingEvent sampleEvent;
bool enable;
@ -239,8 +236,8 @@ void GBAudioWriteNR50(struct GBAudio* audio, uint8_t);
void GBAudioWriteNR51(struct GBAudio* audio, uint8_t);
void GBAudioWriteNR52(struct GBAudio* audio, uint8_t);
void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels);
void GBAudioUpdateFrame(struct GBAudio* audio);
void GBAudioUpdateChannel4(struct GBAudio* audio);
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right);

View File

@ -19,7 +19,7 @@ extern MGBA_EXPORT const uint32_t GBSavestateVersion;
mLOG_DECLARE_CATEGORY(GB_STATE);
/* Savestate format:
* 0x00000 - 0x00003: Version Magic (0x01000002)
* 0x00000 - 0x00003: Version Magic (0x00400003)
* 0x00004 - 0x00007: ROM CRC32
* 0x00008: Game Boy model
* 0x00009 - 0x0000B: Reserved (leave zero)
@ -56,20 +56,23 @@ mLOG_DECLARE_CATEGORY(GB_STATE);
* | bits 0 - 6: Remaining length
* | bits 7 - 9: Next step
* | bits 10 - 20: Shadow frequency register
* | bits 21 - 31: Reserved
* | bits 21 - 23: Duty index
* | bits 24 - 31: Reserved
* | 0x0004C - 0x0004F: Next frame
* | 0x00050 - 0x00053: Next channel 3 fade
* | 0x00050 - 0x00053: Reserved
* | 0x00054 - 0x00057: Sweep state
* | bits 0 - 2: Timesteps
* | bits 3 - 31: Reserved
* | 0x00058 - 0x0005B: Next event
* | 0x00058 - 0x0005B: Last update
* 0x0005C - 0x0006B: Audio channel 2 state
* | 0x0005C - 0x0005F: Envelepe timing
* | bits 0 - 2: Remaining length
* | bits 3 - 5: Next step
* | bits 6 - 31: Reserved
* | bits 6 - 20: Reserved
* | bits 21 - 23: Duty index
* | bits 24 - 31: Reserved
* | 0x00060 - 0x00067: Reserved
* | 0x00068 - 0x0006B: Next event
* | 0x00068 - 0x0006B: Last update
* 0x0006C - 0x00093: Audio channel 3 state
* | 0x0006C - 0x0008B: Wave banks
* | 0x0008C - 0x0008D: Remaining length
@ -208,7 +211,7 @@ DECL_BITFIELD(GBSerializedAudioEnvelope, uint32_t);
DECL_BITS(GBSerializedAudioEnvelope, Length, 0, 7);
DECL_BITS(GBSerializedAudioEnvelope, NextStep, 7, 3);
DECL_BITS(GBSerializedAudioEnvelope, Frequency, 10, 11);
DECL_BITS(GBSerializedAudioEnvelope, DutyIndex, 21, 3);
DECL_BITFIELD(GBSerializedAudioSweep, uint32_t);
DECL_BITS(GBSerializedAudioSweep, Time, 0, 3);
@ -217,14 +220,14 @@ struct GBSerializedPSGState {
struct {
GBSerializedAudioEnvelope envelope;
int32_t nextFrame;
int32_t nextCh3Fade;
int32_t reserved;
GBSerializedAudioSweep sweep;
uint32_t nextEvent;
uint32_t lastUpdate;
} ch1;
struct {
GBSerializedAudioEnvelope envelope;
int32_t reserved[2];
int32_t nextEvent;
uint32_t lastUpdate;
} ch2;
struct {
uint32_t wavebanks[8];

View File

@ -16,6 +16,7 @@ CXX_GUARD_START
#include <mgba-util/circle-buffer.h>
#define GBA_AUDIO_FIFO_SIZE 8
#define GBA_MAX_SAMPLES 16
#define MP2K_MAGIC 0x68736D53
#define MP2K_MAX_SOUND_CHANNELS 12
@ -34,7 +35,7 @@ struct GBAAudioFIFO {
uint32_t internalSample;
int internalRemaining;
int dmaSource;
int8_t sample;
int8_t samples[GBA_MAX_SAMPLES];
};
DECL_BITFIELD(GBARegisterSOUNDCNT_HI, uint16_t);

View File

@ -20,7 +20,7 @@ extern MGBA_EXPORT const uint32_t GBASavestateVersion;
mLOG_DECLARE_CATEGORY(GBA_STATE);
/* Savestate format:
* 0x00000 - 0x00003: Version Magic (0x01000004)
* 0x00000 - 0x00003: Version Magic (0x01000005)
* 0x00004 - 0x00007: BIOS checksum (e.g. 0xBAAE187F for official BIOS)
* 0x00008 - 0x0000B: ROM CRC32
* 0x0000C - 0x0000F: Master cycles
@ -39,20 +39,23 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* | bits 0 - 6: Remaining length
* | bits 7 - 9: Next step
* | bits 10 - 20: Shadow frequency register
* | bits 21 - 31: Reserved
* | bits 21 - 23: Duty index
* | bits 24 - 31: Reserved
* | 0x00134 - 0x00137: Next frame
* | 0x00138 - 0x0013B: Next channel 3 fade
* | 0x00138 - 0x0013B: Reserved
* | 0x0013C - 0x0013F: Sweep state
* | bits 0 - 2: Timesteps
* | bits 3 - 7: Reserved
* | 0x00140 - 0x00143: Next event
* | 0x00140 - 0x00143: Last update
* 0x00144 - 0x00153: Audio channel 2 state
* | 0x00144 - 0x00147: Envelepe timing
* | bits 0 - 2: Remaining length
* | bits 3 - 5: Next step
* | bits 6 - 31: Reserved
* | bits 6 - 20: Reserved
* | bits 21 - 23: Duty index
* | bits 24 - 31: Reserved
* | 0x00148 - 0x0014F: Reserved
* | 0x00150 - 0x00153: Next event
* | 0x00150 - 0x00153: Last update
* 0x00154 - 0x0017B: Audio channel 3 state
* | 0x00154 - 0x00173: Wave banks
* | 0x00174 - 0x00175: Remaining length
@ -221,7 +224,10 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* 0x00320 - 0x00323: Next IRQ event
* 0x00324 - 0x00327: Interruptable BIOS stall cycles
* 0x00328 - 0x00367: Matrix memory mapping table
* 0x00368 - 0x003FF: Reserved (leave zero)
* 0x00368 - 0x0036F: Reserved (leave zero)
* 0x00370 - 0x0037F: Audio FIFO A samples
* 0x00380 - 0x0038F: Audio FIFO B samples
* 0x00380 - 0x003FF: Reserved (leave zero)
* 0x00400 - 0x007FF: I/O memory
* 0x00800 - 0x00BFF: Palette
* 0x00C00 - 0x00FFF: OAM
@ -378,8 +384,14 @@ struct GBASerializedState {
int32_t biosStall;
uint32_t matrixMappings[16];
uint32_t reservedMatrix[2];
uint32_t reserved[38];
struct {
int8_t chA[16];
int8_t chB[16];
} samples;
uint32_t reserved[28];
uint16_t io[SIZE_IO >> 1];
uint16_t pram[SIZE_PALETTE_RAM >> 1];

View File

@ -14,8 +14,9 @@ CXX_GUARD_START
#include <mgba-util/table.h>
#include <mgba-util/vfs.h>
#define mSCRIPT_KV_PAIR(KEY, VALUE) { #KEY, VALUE }
#define mSCRIPT_CONSTANT_PAIR(NS, CONST) { #CONST, mScriptValueCreateFromSInt(NS ## _ ## CONST) }
#define mSCRIPT_CONSTANT_SENTINEL { NULL, NULL }
#define mSCRIPT_KV_SENTINEL { NULL, NULL }
struct mScriptFrame;
struct mScriptFunction;
@ -29,6 +30,7 @@ struct mScriptContext {
uint32_t nextWeakref;
struct Table callbacks;
struct mScriptValue* constants;
struct Table docstrings;
};
struct mScriptEngine2 {
@ -80,10 +82,14 @@ void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref);
void mScriptContextAttachStdlib(struct mScriptContext* context);
void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants);
void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value);
void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback);
void mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value);
void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring);
const char* mScriptContextGetDocstring(struct mScriptContext*, const char* key);
struct VFile;
bool mScriptContextLoadVF(struct mScriptContext*, const char* name, struct VFile* vf);
bool mScriptContextLoadFile(struct mScriptContext*, const char* path);

View File

@ -41,6 +41,7 @@ CXX_GUARD_START
#define mSCRIPT_TYPE_C_PS(X) void
#define mSCRIPT_TYPE_C_PCS(X) void
#define mSCRIPT_TYPE_C_WSTR struct mScriptValue*
#define mSCRIPT_TYPE_C_WLIST struct mScriptValue*
#define mSCRIPT_TYPE_C_W(X) struct mScriptValue*
#define mSCRIPT_TYPE_C_CW(X) const struct mScriptValue*
@ -67,6 +68,7 @@ CXX_GUARD_START
#define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque
#define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque
#define mSCRIPT_TYPE_FIELD_WSTR opaque
#define mSCRIPT_TYPE_FIELD_WLIST opaque
#define mSCRIPT_TYPE_FIELD_W(TYPE) opaque
#define mSCRIPT_TYPE_FIELD_CW(TYPE) opaque
@ -92,6 +94,7 @@ CXX_GUARD_START
#define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT)
#define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructConstPtr_ ## STRUCT)
#define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper)
#define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper)
#define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE)
#define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE)
@ -108,6 +111,7 @@ CXX_GUARD_START
#define mSCRIPT_TYPE_CMP_F64(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_F64, TYPE)
#define mSCRIPT_TYPE_CMP_STR(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE)
#define mSCRIPT_TYPE_CMP_CHARP(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)
#define mSCRIPT_TYPE_CMP_LIST(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)
#define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE)
#define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true)
#define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME
@ -115,6 +119,7 @@ CXX_GUARD_START
#define mSCRIPT_TYPE_CMP_S_METHOD(STRUCT, NAME) mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME)->name == _mSCRIPT_FIELD_NAME
#define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1)
#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE))
#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE))
enum mScriptTypeBase {
mSCRIPT_TYPE_VOID = 0,
@ -167,6 +172,7 @@ extern const struct mScriptType mSTTable;
extern const struct mScriptType mSTWrapper;
extern const struct mScriptType mSTWeakref;
extern const struct mScriptType mSTStringWrapper;
extern const struct mScriptType mSTListWrapper;
struct mScriptType;
struct mScriptValue {

View File

@ -68,22 +68,16 @@ local GBAGameEn = Game:new{
local Generation1En = GBGameEn:new{
_boxMonSize=33,
_partyMonSize=44,
_readBoxMon=readBoxMonGen1,
_readPartyMon=readPartyMonGen1,
}
local Generation2En = GBGameEn:new{
_boxMonSize=32,
_partyMonSize=48,
_readBoxMon=readBoxMonGen2,
_readPartyMon=readPartyMonGen2,
}
local Generation3En = GBAGameEn:new{
_boxMonSize=80,
_partyMonSize=100,
_readBoxMon=readBoxMonGen3,
_readPartyMon=readPartyMonGen3,
}
GBGameEn._charmap = { [0]=
@ -475,7 +469,6 @@ gameCrc32 = {
[0x9f7fdd53] = gameRBEn, -- Red
[0xd6da8a1a] = gameRBEn, -- Blue
[0x7d527d62] = gameYellowEn,
[0x3358e30a] = gameCrystal, -- Crystal rev 1
[0x84ee4776] = gameFireRedEnR1,
[0xdaffecec] = gameLeafGreenEnR1,
}

View File

@ -710,14 +710,20 @@ mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptConsole, createBuffer)
mSCRIPT_CHARP(NULL)
mSCRIPT_DEFINE_DEFAULTS_END;
void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger* logger) {
static struct mScriptConsole* _ensureConsole(struct mScriptContext* context) {
struct mScriptValue* value = mScriptContextEnsureGlobal(context, "console", mSCRIPT_TYPE_MS_S(mScriptConsole));
struct mScriptConsole* console = value->value.opaque;
if (!console) {
console = calloc(1, sizeof(*console));
value->value.opaque = console;
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
mScriptContextSetDocstring(context, "console", "Singleton instance of struct::mScriptConsole");
}
return console;
}
void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger* logger) {
struct mScriptConsole* console = _ensureConsole(context);
console->logger = logger;
}
@ -771,14 +777,7 @@ mSCRIPT_DEFINE_STRUCT(mScriptTextBuffer)
mSCRIPT_DEFINE_END;
void mScriptContextSetTextBufferFactory(struct mScriptContext* context, mScriptContextBufferFactory factory, void* cbContext) {
struct mScriptValue* value = mScriptContextEnsureGlobal(context, "console", mSCRIPT_TYPE_MS_S(mScriptConsole));
struct mScriptConsole* console = value->value.opaque;
if (!console) {
console = calloc(1, sizeof(*console));
console->logger = mLogGetContext();
value->value.opaque = console;
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
}
struct mScriptConsole* console = _ensureConsole(context);
console->textBufferFactory = factory;
console->textBufferContext = cbContext;
}

View File

@ -173,25 +173,73 @@ int main(int argc, char* argv[]) {
prefix = false;
needsUnmount = true;
#endif
} else {
} else if (strcmp(extension, "appimage") != 0) {
archive = VDirOpenArchive(updateArchive);
}
if (!archive) {
puts("Cannot open update archive");
} else {
if (archive) {
puts("Extracting update");
if (extractArchive(archive, root, prefix)) {
puts("Complete");
const char* command = mUpdateGetCommand(&config);
strlcpy(bin, command, sizeof(bin));
ok = 0;
mUpdateDeregister(&config);
} else {
puts("An error occurred");
}
archive->close(archive);
unlink(updateArchive);
}
#ifdef __linux__
else if (strcmp(extension, "appimage") == 0) {
const char* command = mUpdateGetCommand(&config);
strlcpy(bin, command, sizeof(bin));
if (rename(updateArchive, bin) < 0) {
if (errno == EXDEV) {
// Cross-dev, need to copy manually
int infd = open(updateArchive, O_RDONLY);
int outfd = -1;
if (infd >= 0) {
ok = 2;
} else {
outfd = open(bin, O_CREAT | O_WRONLY | O_TRUNC, 0755);
}
if (outfd < 0) {
ok = 2;
} else {
uint8_t buffer[2048];
ssize_t size;
while ((size = read(infd, buffer, sizeof(buffer))) > 0) {
if (write(outfd, buffer, size) < size) {
ok = 2;
break;
}
}
if (size < 0) {
ok = 2;
}
close(outfd);
close(infd);
}
if (ok == 2) {
puts("Cannot move update over old file");
}
} else {
puts("Cannot move update over old file");
}
} else {
ok = 0;
}
if (ok == 0) {
chmod(bin, 0755);
}
}
#endif
else {
puts("Cannot open update archive");
}
if (ok == 0) {
puts("Complete");
const char* command = mUpdateGetCommand(&config);
strlcpy(bin, command, sizeof(bin));
mUpdateDeregister(&config);
}
#ifdef __APPLE__
if (needsUnmount) {
char* args[] = {"hdiutil", "detach", devpath, NULL};

View File

@ -35,19 +35,19 @@ static void _updateEnvelopeDead(struct GBAudioEnvelope* envelope);
static bool _updateSweep(struct GBAudioSquareChannel* sweep, bool initial);
static void _updateSquareSample(struct GBAudioSquareChannel* ch);
static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch);
static int32_t _cyclesToInvert(struct GBAudioSquareChannel* ch);
static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch);
static void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate);
static void _updateChannel1(struct mTiming* timing, void* user, uint32_t cyclesLate);
static void _updateChannel2(struct mTiming* timing, void* user, uint32_t cyclesLate);
static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate);
static void _fadeChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate);
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate);
static const int _squareChannelDuty[4][8] = {
{ 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 1, 1, 1 },
{ 0, 1, 1, 1, 1, 1, 1, 0 },
};
void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAudioStyle style) {
audio->samples = samples;
audio->left = blip_new(BLIP_BUFFER_SIZE);
@ -73,26 +73,6 @@ void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAu
audio->frameEvent.name = "GB Audio Frame Sequencer";
audio->frameEvent.callback = _updateFrame;
audio->frameEvent.priority = 0x10;
audio->ch1Event.context = audio;
audio->ch1Event.name = "GB Audio Channel 1";
audio->ch1Event.callback = _updateChannel1;
audio->ch1Event.priority = 0x11;
audio->ch2Event.context = audio;
audio->ch2Event.name = "GB Audio Channel 2";
audio->ch2Event.callback = _updateChannel2;
audio->ch2Event.priority = 0x12;
audio->ch3Event.context = audio;
audio->ch3Event.name = "GB Audio Channel 3";
audio->ch3Event.callback = _updateChannel3;
audio->ch3Event.priority = 0x13;
audio->ch3Fade.context = audio;
audio->ch3Fade.name = "GB Audio Channel 3 Memory";
audio->ch3Fade.callback = _fadeChannel3;
audio->ch3Fade.priority = 0x14;
audio->ch4Event.context = audio;
audio->ch4Event.name = "GB Audio Channel 4";
audio->ch4Event.callback = NULL; // This is pending removal, so calling it will crash
audio->ch4Event.priority = 0x15;
audio->sampleEvent.context = audio;
audio->sampleEvent.name = "GB Audio Sample";
audio->sampleEvent.callback = _sample;
@ -106,11 +86,6 @@ void GBAudioDeinit(struct GBAudio* audio) {
void GBAudioReset(struct GBAudio* audio) {
mTimingDeschedule(audio->timing, &audio->frameEvent);
mTimingDeschedule(audio->timing, &audio->ch1Event);
mTimingDeschedule(audio->timing, &audio->ch2Event);
mTimingDeschedule(audio->timing, &audio->ch3Event);
mTimingDeschedule(audio->timing, &audio->ch3Fade);
mTimingDeschedule(audio->timing, &audio->ch4Event);
mTimingDeschedule(audio->timing, &audio->sampleEvent);
if (audio->style != GB_AUDIO_GBA) {
mTimingSchedule(audio->timing, &audio->sampleEvent, 0);
@ -168,32 +143,35 @@ void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) {
}
void GBAudioWriteNR10(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
if (!_writeSweep(&audio->ch1.sweep, value)) {
mTimingDeschedule(audio->timing, &audio->ch1Event);
audio->playingCh1 = false;
*audio->nr52 &= ~0x0001;
}
}
void GBAudioWriteNR11(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
_writeDuty(&audio->ch1.envelope, value);
audio->ch1.control.length = 64 - audio->ch1.envelope.length;
}
void GBAudioWriteNR12(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
if (!_writeEnvelope(&audio->ch1.envelope, value, audio->style)) {
mTimingDeschedule(audio->timing, &audio->ch1Event);
audio->playingCh1 = false;
*audio->nr52 &= ~0x0001;
}
}
void GBAudioWriteNR13(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
audio->ch1.control.frequency &= 0x700;
audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value);
}
void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
audio->ch1.control.frequency &= 0xFF;
audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8);
bool wasStop = audio->ch1.control.stop;
@ -201,12 +179,10 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) {
if (!wasStop && audio->ch1.control.stop && audio->ch1.control.length && !(audio->frame & 1)) {
--audio->ch1.control.length;
if (audio->ch1.control.length == 0) {
mTimingDeschedule(audio->timing, &audio->ch1Event);
audio->playingCh1 = false;
}
}
if (GBAudioRegisterControlIsRestart(value << 8)) {
bool wasDead = !audio->playingCh1;
audio->playingCh1 = _resetEnvelope(&audio->ch1.envelope);
audio->ch1.sweep.realFrequency = audio->ch1.control.frequency;
_resetSweep(&audio->ch1.sweep);
@ -220,35 +196,33 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) {
}
}
_updateSquareSample(&audio->ch1);
if (wasDead && audio->playingCh1) {
mTimingSchedule(audio->timing, &audio->ch1Event, _cyclesToInvert(&audio->ch1));
} else if (!audio->playingCh1) {
mTimingDeschedule(audio->timing, &audio->ch1Event);
}
}
*audio->nr52 &= ~0x0001;
*audio->nr52 |= audio->playingCh1;
}
void GBAudioWriteNR21(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
_writeDuty(&audio->ch2.envelope, value);
audio->ch2.control.length = 64 - audio->ch2.envelope.length;
}
void GBAudioWriteNR22(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
if (!_writeEnvelope(&audio->ch2.envelope, value, audio->style)) {
mTimingDeschedule(audio->timing, &audio->ch2Event);
audio->playingCh2 = false;
*audio->nr52 &= ~0x0002;
}
}
void GBAudioWriteNR23(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
audio->ch2.control.frequency &= 0x700;
audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value);
}
void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
audio->ch2.control.frequency &= 0xFF;
audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8);
bool wasStop = audio->ch2.control.stop;
@ -256,12 +230,10 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) {
if (!wasStop && audio->ch2.control.stop && audio->ch2.control.length && !(audio->frame & 1)) {
--audio->ch2.control.length;
if (audio->ch2.control.length == 0) {
mTimingDeschedule(audio->timing, &audio->ch2Event);
audio->playingCh2 = false;
}
}
if (GBAudioRegisterControlIsRestart(value << 8)) {
bool wasDead = !audio->playingCh2;
audio->playingCh2 = _resetEnvelope(&audio->ch2.envelope);
if (!audio->ch2.control.length) {
@ -271,39 +243,38 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) {
}
}
_updateSquareSample(&audio->ch2);
if (wasDead && audio->playingCh2) {
mTimingSchedule(audio->timing, &audio->ch2Event, _cyclesToInvert(&audio->ch2));
} else if (!audio->playingCh2) {
mTimingDeschedule(audio->timing, &audio->ch2Event);
}
}
*audio->nr52 &= ~0x0002;
*audio->nr52 |= audio->playingCh2 << 1;
}
void GBAudioWriteNR30(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
audio->ch3.enable = GBAudioRegisterBankGetEnable(value);
if (!audio->ch3.enable) {
mTimingDeschedule(audio->timing, &audio->ch3Event);
audio->playingCh3 = false;
*audio->nr52 &= ~0x0004;
}
}
void GBAudioWriteNR31(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
audio->ch3.length = 256 - value;
}
void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
audio->ch3.volume = GBAudioRegisterBankVolumeGetVolumeGB(value);
}
void GBAudioWriteNR33(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
audio->ch3.rate &= 0x700;
audio->ch3.rate |= GBAudioRegisterControlGetRate(value);
}
void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
audio->ch3.rate &= 0xFF;
audio->ch3.rate |= GBAudioRegisterControlGetRate(value << 8);
bool wasStop = audio->ch3.stop;
@ -339,25 +310,23 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) {
audio->ch3.sample = 0;
}
}
mTimingDeschedule(audio->timing, &audio->ch3Fade);
mTimingDeschedule(audio->timing, &audio->ch3Event);
if (audio->playingCh3) {
audio->ch3.readable = audio->style != GB_AUDIO_DMG;
// TODO: Where does this cycle delay come from?
mTimingSchedule(audio->timing, &audio->ch3Event, audio->timingFactor * (4 + 2 * (2048 - audio->ch3.rate)));
audio->ch3.nextUpdate = mTimingCurrentTime(audio->timing) + (6 + 2 * (2048 - audio->ch3.rate)) * audio->timingFactor;
}
*audio->nr52 &= ~0x0004;
*audio->nr52 |= audio->playingCh3 << 2;
}
void GBAudioWriteNR41(struct GBAudio* audio, uint8_t value) {
GBAudioUpdateChannel4(audio);
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8);
_writeDuty(&audio->ch4.envelope, value);
audio->ch4.length = 64 - audio->ch4.envelope.length;
}
void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) {
GBAudioUpdateChannel4(audio);
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8);
if (!_writeEnvelope(&audio->ch4.envelope, value, audio->style)) {
audio->playingCh4 = false;
*audio->nr52 &= ~0x0008;
@ -365,14 +334,14 @@ void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) {
}
void GBAudioWriteNR43(struct GBAudio* audio, uint8_t value) {
GBAudioUpdateChannel4(audio);
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8);
audio->ch4.ratio = GBAudioRegisterNoiseFeedbackGetRatio(value);
audio->ch4.frequency = GBAudioRegisterNoiseFeedbackGetFrequency(value);
audio->ch4.power = GBAudioRegisterNoiseFeedbackGetPower(value);
}
void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
GBAudioUpdateChannel4(audio);
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8);
bool wasStop = audio->ch4.stop;
audio->ch4.stop = GBAudioRegisterNoiseControlGetStop(value);
if (!wasStop && audio->ch4.stop && audio->ch4.length && !(audio->frame & 1)) {
@ -395,7 +364,7 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
--audio->ch4.length;
}
}
if (audio->playingCh4 && audio->ch4.envelope.dead != 2) {
if (audio->playingCh4) {
audio->ch4.lastEvent = mTimingCurrentTime(audio->timing);
}
}
@ -496,6 +465,132 @@ void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate) {
}
}
void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
if (!audio->enable) {
return;
}
if (audio->playingCh1 && (channels & 0x1)) {
int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor;
int32_t diff = timestamp - audio->ch1.lastUpdate;
if (diff >= period) {
diff /= period;
audio->ch1.index = (audio->ch1.index + diff) & 7;
audio->ch1.lastUpdate += diff * period;
_updateSquareSample(&audio->ch1);
}
}
if (audio->playingCh2 && (channels & 0x2)) {
int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor;
int32_t diff = timestamp - audio->ch2.lastUpdate;
if (diff >= period) {
diff /= period;
audio->ch2.index = (audio->ch2.index + diff) & 7;
audio->ch2.lastUpdate += diff * period;
_updateSquareSample(&audio->ch2);
}
}
if (audio->playingCh3 && (channels & 0x4)) {
int cycles = 2 * (2048 - audio->ch3.rate) * audio->timingFactor;
int32_t diff = timestamp - audio->ch3.nextUpdate;
if (diff >= 0) {
diff = (diff / cycles) + 1;
int volume;
switch (audio->ch3.volume) {
case 0:
volume = 4;
break;
case 1:
volume = 0;
break;
case 2:
volume = 1;
break;
default:
case 3:
volume = 2;
break;
}
int start = 7;
int end = 0;
int mask = 0x1F;
int iter;
switch (audio->style) {
case GB_AUDIO_DMG:
default:
audio->ch3.window += diff;
audio->ch3.window &= 0x1F;
audio->ch3.sample = audio->ch3.wavedata8[audio->ch3.window >> 1];
if (!(audio->ch3.window & 1)) {
audio->ch3.sample >>= 4;
}
audio->ch3.sample &= 0xF;
break;
case GB_AUDIO_GBA:
if (audio->ch3.size) {
mask = 0x3F;
} else if (audio->ch3.bank) {
end = 4;
} else {
start = 3;
}
for (iter = 0; iter < (diff & mask); ++iter) {
uint32_t bitsCarry = audio->ch3.wavedata32[end] & 0x000000F0;
uint32_t bits;
int i;
for (i = start; i >= end; --i) {
bits = audio->ch3.wavedata32[i] & 0x000000F0;
audio->ch3.wavedata32[i] = ((audio->ch3.wavedata32[i] & 0x0F0F0F0F) << 4) | ((audio->ch3.wavedata32[i] & 0xF0F0F000) >> 12);
audio->ch3.wavedata32[i] |= bitsCarry << 20;
bitsCarry = bits;
}
audio->ch3.sample = bitsCarry >> 4;
}
break;
}
if (audio->ch3.volume > 3) {
audio->ch3.sample += audio->ch3.sample << 1;
}
audio->ch3.sample >>= volume;
audio->ch3.nextUpdate += diff * cycles;
audio->ch3.readable = true;
}
if (audio->style == GB_AUDIO_DMG && audio->ch3.readable) {
diff = timestamp - audio->ch3.nextUpdate + cycles;
if (diff >= 4) {
audio->ch3.readable = false;
}
}
}
if (audio->playingCh4 && (channels & 0x8)) {
int32_t cycles = audio->ch4.ratio ? 2 * audio->ch4.ratio : 1;
cycles <<= audio->ch4.frequency;
cycles *= 8 * audio->timingFactor;
int32_t diff = timestamp - audio->ch4.lastEvent;
if (diff >= cycles) {
int32_t last;
int samples = 0;
int positiveSamples = 0;
int lsb;
int coeff = 0x60;
if (!audio->ch4.power) {
coeff <<= 8;
}
for (last = 0; last + cycles <= diff; last += cycles) {
lsb = audio->ch4.lfsr & 1;
audio->ch4.lfsr >>= 1;
audio->ch4.lfsr ^= lsb * coeff;
++samples;
positiveSamples += lsb;
}
audio->ch4.sample = lsb * audio->ch4.envelope.currentVolume;
audio->ch4.nSamples += samples;
audio->ch4.samples += positiveSamples * audio->ch4.envelope.currentVolume;
audio->ch4.lastEvent += last;
}
}
}
void GBAudioUpdateFrame(struct GBAudio* audio) {
if (!audio->enable) {
return;
@ -504,6 +599,8 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
audio->skipFrame = false;
return;
}
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x7);
int frame = (audio->frame + 1) & 7;
audio->frame = frame;
@ -516,9 +613,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
audio->playingCh1 = _updateSweep(&audio->ch1, false);
*audio->nr52 &= ~0x0001;
*audio->nr52 |= audio->playingCh1;
if (!audio->playingCh1) {
mTimingDeschedule(audio->timing, &audio->ch1Event);
}
}
}
// Fall through
@ -527,7 +621,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
if (audio->ch1.control.length && audio->ch1.control.stop) {
--audio->ch1.control.length;
if (audio->ch1.control.length == 0) {
mTimingDeschedule(audio->timing, &audio->ch1Event);
audio->playingCh1 = 0;
*audio->nr52 &= ~0x0001;
}
@ -536,7 +629,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
if (audio->ch2.control.length && audio->ch2.control.stop) {
--audio->ch2.control.length;
if (audio->ch2.control.length == 0) {
mTimingDeschedule(audio->timing, &audio->ch2Event);
audio->playingCh2 = 0;
*audio->nr52 &= ~0x0002;
}
@ -545,7 +637,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
if (audio->ch3.length && audio->ch3.stop) {
--audio->ch3.length;
if (audio->ch3.length == 0) {
mTimingDeschedule(audio->timing, &audio->ch3Event);
audio->playingCh3 = 0;
*audio->nr52 &= ~0x0004;
}
@ -554,7 +645,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
if (audio->ch4.length && audio->ch4.stop) {
--audio->ch4.length;
if (audio->ch4.length == 0) {
GBAudioUpdateChannel4(audio);
audio->playingCh4 = 0;
*audio->nr52 &= ~0x0008;
}
@ -565,9 +655,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
--audio->ch1.envelope.nextStep;
if (audio->ch1.envelope.nextStep == 0) {
_updateEnvelope(&audio->ch1.envelope);
if (audio->ch1.envelope.dead == 2) {
mTimingDeschedule(audio->timing, &audio->ch1Event);
}
_updateSquareSample(&audio->ch1);
}
}
@ -576,9 +663,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
--audio->ch2.envelope.nextStep;
if (audio->ch2.envelope.nextStep == 0) {
_updateEnvelope(&audio->ch2.envelope);
if (audio->ch2.envelope.dead == 2) {
mTimingDeschedule(audio->timing, &audio->ch2Event);
}
_updateSquareSample(&audio->ch2);
}
}
@ -586,7 +670,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
if (audio->playingCh4 && !audio->ch4.envelope.dead) {
--audio->ch4.envelope.nextStep;
if (audio->ch4.envelope.nextStep == 0) {
GBAudioUpdateChannel4(audio);
int8_t sample = audio->ch4.sample;
_updateEnvelope(&audio->ch4.envelope);
audio->ch4.sample = (sample > 0) * audio->ch4.envelope.currentVolume;
@ -600,31 +683,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
}
}
void GBAudioUpdateChannel4(struct GBAudio* audio) {
struct GBAudioNoiseChannel* ch = &audio->ch4;
if (ch->envelope.dead == 2 || !audio->playingCh4) {
return;
}
int32_t cycles = ch->ratio ? 2 * ch->ratio : 1;
cycles <<= ch->frequency;
cycles *= 8 * audio->timingFactor;
uint32_t last = 0;
uint32_t now = mTimingCurrentTime(audio->timing) - ch->lastEvent;
for (; last + cycles <= now; last += cycles) {
int lsb = ch->lfsr & 1;
ch->sample = lsb * ch->envelope.currentVolume;
++ch->nSamples;
ch->samples += ch->sample;
ch->lfsr >>= 1;
ch->lfsr ^= (lsb * 0x60) << (ch->power ? 0 : 8);
}
ch->lastEvent += last;
}
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : -0x8;
int sampleLeft = dcOffset;
@ -664,7 +722,6 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
sampleRight <<= 3;
if (!audio->forceDisableCh[3]) {
GBAudioUpdateChannel4(audio);
int16_t sample = audio->style == GB_AUDIO_GBA ? (audio->ch4.sample << 3) : _coalesceNoiseChannel(&audio->ch4);
if (audio->ch4Left) {
sampleLeft += sample;
@ -683,6 +740,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
struct GBAudio* audio = user;
int16_t sampleLeft = 0;
int16_t sampleRight = 0;
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF);
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
@ -775,30 +833,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value, enum GBAudi
}
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
ch->sample = ch->control.hi * ch->envelope.currentVolume;
}
static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) {
ch->control.hi = !ch->control.hi;
_updateSquareSample(ch);
return _cyclesToInvert(ch);
}
static int32_t _cyclesToInvert(struct GBAudioSquareChannel* ch) {
int period = 4 * (2048 - ch->control.frequency);
switch (ch->envelope.duty) {
case 0:
return ch->control.hi ? period : period * 7;
case 1:
return ch->control.hi ? period * 2 : period * 6;
case 2:
return period * 4;
case 3:
return ch->control.hi ? period * 6 : period * 2;
default:
// This should never be hit
return period * 4;
}
ch->sample = _squareChannelDuty[ch->envelope.duty][ch->index] * ch->envelope.currentVolume;
}
static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch) {
@ -870,94 +905,6 @@ static bool _updateSweep(struct GBAudioSquareChannel* ch, bool initial) {
return true;
}
static void _updateChannel1(struct mTiming* timing, void* user, uint32_t cyclesLate) {
struct GBAudio* audio = user;
struct GBAudioSquareChannel* ch = &audio->ch1;
int cycles = _updateSquareChannel(ch);
mTimingSchedule(timing, &audio->ch1Event, audio->timingFactor * cycles - cyclesLate);
}
static void _updateChannel2(struct mTiming* timing, void* user, uint32_t cyclesLate) {
struct GBAudio* audio = user;
struct GBAudioSquareChannel* ch = &audio->ch2;
int cycles = _updateSquareChannel(ch);
mTimingSchedule(timing, &audio->ch2Event, audio->timingFactor * cycles - cyclesLate);
}
static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate) {
struct GBAudio* audio = user;
struct GBAudioWaveChannel* ch = &audio->ch3;
int i;
int volume;
switch (ch->volume) {
case 0:
volume = 4;
break;
case 1:
volume = 0;
break;
case 2:
volume = 1;
break;
default:
case 3:
volume = 2;
break;
}
int start;
int end;
switch (audio->style) {
case GB_AUDIO_DMG:
default:
++ch->window;
ch->window &= 0x1F;
ch->sample = ch->wavedata8[ch->window >> 1];
if (!(ch->window & 1)) {
ch->sample >>= 4;
}
ch->sample &= 0xF;
break;
case GB_AUDIO_GBA:
if (ch->size) {
start = 7;
end = 0;
} else if (ch->bank) {
start = 7;
end = 4;
} else {
start = 3;
end = 0;
}
uint32_t bitsCarry = ch->wavedata32[end] & 0x000000F0;
uint32_t bits;
for (i = start; i >= end; --i) {
bits = ch->wavedata32[i] & 0x000000F0;
ch->wavedata32[i] = ((ch->wavedata32[i] & 0x0F0F0F0F) << 4) | ((ch->wavedata32[i] & 0xF0F0F000) >> 12);
ch->wavedata32[i] |= bitsCarry << 20;
bitsCarry = bits;
}
ch->sample = bitsCarry >> 4;
break;
}
if (ch->volume > 3) {
ch->sample += ch->sample << 1;
}
ch->sample >>= volume;
audio->ch3.readable = true;
if (audio->style == GB_AUDIO_DMG) {
mTimingDeschedule(audio->timing, &audio->ch3Fade);
mTimingSchedule(timing, &audio->ch3Fade, 4 - cyclesLate);
}
int cycles = 2 * (2048 - ch->rate);
mTimingSchedule(timing, &audio->ch3Event, audio->timingFactor * cycles - cyclesLate);
}
static void _fadeChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate) {
UNUSED(timing);
UNUSED(cyclesLate);
struct GBAudio* audio = user;
audio->ch3.readable = false;
}
void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut) {
uint32_t flags = 0;
uint32_t sweep = 0;
@ -971,30 +918,29 @@ void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGStat
flags = GBSerializedAudioFlagsSetCh1Volume(flags, audio->ch1.envelope.currentVolume);
flags = GBSerializedAudioFlagsSetCh1Dead(flags, audio->ch1.envelope.dead);
flags = GBSerializedAudioFlagsSetCh1Hi(flags, audio->ch1.control.hi);
flags = GBSerializedAudioFlagsSetCh1SweepEnabled(flags, audio->ch1.sweep.enable);
flags = GBSerializedAudioFlagsSetCh1SweepOccurred(flags, audio->ch1.sweep.occurred);
ch1Flags = GBSerializedAudioEnvelopeSetLength(ch1Flags, audio->ch1.control.length);
ch1Flags = GBSerializedAudioEnvelopeSetNextStep(ch1Flags, audio->ch1.envelope.nextStep);
ch1Flags = GBSerializedAudioEnvelopeSetFrequency(ch1Flags, audio->ch1.sweep.realFrequency);
ch1Flags = GBSerializedAudioEnvelopeSetDutyIndex(ch1Flags, audio->ch1.index);
sweep = GBSerializedAudioSweepSetTime(sweep, audio->ch1.sweep.time & 7);
STORE_32LE(ch1Flags, 0, &state->ch1.envelope);
STORE_32LE(sweep, 0, &state->ch1.sweep);
STORE_32LE(audio->ch1Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch1.nextEvent);
STORE_32LE(audio->ch1.lastUpdate - mTimingCurrentTime(audio->timing), 0, &state->ch1.lastUpdate);
flags = GBSerializedAudioFlagsSetCh2Volume(flags, audio->ch2.envelope.currentVolume);
flags = GBSerializedAudioFlagsSetCh2Dead(flags, audio->ch2.envelope.dead);
flags = GBSerializedAudioFlagsSetCh2Hi(flags, audio->ch2.control.hi);
ch2Flags = GBSerializedAudioEnvelopeSetLength(ch2Flags, audio->ch2.control.length);
ch2Flags = GBSerializedAudioEnvelopeSetNextStep(ch2Flags, audio->ch2.envelope.nextStep);
ch2Flags = GBSerializedAudioEnvelopeSetDutyIndex(ch2Flags, audio->ch2.index);
STORE_32LE(ch2Flags, 0, &state->ch2.envelope);
STORE_32LE(audio->ch2Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch2.nextEvent);
STORE_32LE(audio->ch2.lastUpdate - mTimingCurrentTime(audio->timing), 0, &state->ch2.lastUpdate);
flags = GBSerializedAudioFlagsSetCh3Readable(flags, audio->ch3.readable);
memcpy(state->ch3.wavebanks, audio->ch3.wavedata32, sizeof(state->ch3.wavebanks));
STORE_16LE(audio->ch3.length, 0, &state->ch3.length);
STORE_32LE(audio->ch3Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch3.nextEvent);
STORE_32LE(audio->ch3Fade.when - mTimingCurrentTime(audio->timing), 0, &state->ch1.nextCh3Fade);
STORE_32LE(audio->ch3.nextUpdate - mTimingCurrentTime(audio->timing), 0, &state->ch3.nextEvent);
flags = GBSerializedAudioFlagsSetCh4Volume(flags, audio->ch4.envelope.currentVolume);
flags = GBSerializedAudioFlagsSetCh4Dead(flags, audio->ch4.envelope.dead);
@ -1039,7 +985,6 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt
LOAD_32LE(sweep, 0, &state->ch1.sweep);
audio->ch1.envelope.currentVolume = GBSerializedAudioFlagsGetCh1Volume(flags);
audio->ch1.envelope.dead = GBSerializedAudioFlagsGetCh1Dead(flags);
audio->ch1.control.hi = GBSerializedAudioFlagsGetCh1Hi(flags);
audio->ch1.sweep.enable = GBSerializedAudioFlagsGetCh1SweepEnabled(flags);
audio->ch1.sweep.occurred = GBSerializedAudioFlagsGetCh1SweepOccurred(flags);
audio->ch1.sweep.time = GBSerializedAudioSweepGetTime(sweep);
@ -1049,34 +994,25 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt
audio->ch1.control.length = GBSerializedAudioEnvelopeGetLength(ch1Flags);
audio->ch1.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch1Flags);
audio->ch1.sweep.realFrequency = GBSerializedAudioEnvelopeGetFrequency(ch1Flags);
LOAD_32LE(when, 0, &state->ch1.nextEvent);
if (audio->ch1.envelope.dead < 2 && audio->playingCh1) {
mTimingSchedule(audio->timing, &audio->ch1Event, when);
}
audio->ch1.index = GBSerializedAudioEnvelopeGetDutyIndex(ch1Flags);
LOAD_32LE(audio->ch1.lastUpdate, 0, &state->ch1.lastUpdate);
audio->ch1.lastUpdate += mTimingCurrentTime(audio->timing);
LOAD_32LE(ch2Flags, 0, &state->ch2.envelope);
audio->ch2.envelope.currentVolume = GBSerializedAudioFlagsGetCh2Volume(flags);
audio->ch2.envelope.dead = GBSerializedAudioFlagsGetCh2Dead(flags);
audio->ch2.control.hi = GBSerializedAudioFlagsGetCh2Hi(flags);
audio->ch2.control.length = GBSerializedAudioEnvelopeGetLength(ch2Flags);
audio->ch2.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch2Flags);
LOAD_32LE(when, 0, &state->ch2.nextEvent);
if (audio->ch2.envelope.dead < 2 && audio->playingCh2) {
mTimingSchedule(audio->timing, &audio->ch2Event, when);
}
audio->ch2.index = GBSerializedAudioEnvelopeGetDutyIndex(ch2Flags);
LOAD_32LE(audio->ch2.lastUpdate, 0, &state->ch2.lastUpdate);
audio->ch2.lastUpdate += mTimingCurrentTime(audio->timing);
audio->ch3.readable = GBSerializedAudioFlagsGetCh3Readable(flags);
// TODO: Big endian?
memcpy(audio->ch3.wavedata32, state->ch3.wavebanks, sizeof(audio->ch3.wavedata32));
LOAD_16LE(audio->ch3.length, 0, &state->ch3.length);
LOAD_32LE(when, 0, &state->ch3.nextEvent);
if (audio->playingCh3) {
mTimingSchedule(audio->timing, &audio->ch3Event, when);
}
LOAD_32LE(when, 0, &state->ch1.nextCh3Fade);
if (audio->ch3.readable && audio->style == GB_AUDIO_DMG) {
mTimingSchedule(audio->timing, &audio->ch3Fade, when);
}
LOAD_32LE(audio->ch3.nextUpdate, 0, &state->ch3.nextEvent);
audio->ch3.nextUpdate += mTimingCurrentTime(audio->timing);
LOAD_32LE(ch4Flags, 0, &state->ch4.envelope);
audio->ch4.envelope.currentVolume = GBSerializedAudioFlagsGetCh4Volume(flags);

View File

@ -403,6 +403,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
case GB_REG_WAVE_D:
case GB_REG_WAVE_E:
case GB_REG_WAVE_F:
GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0x4);
if (!gb->audio.playingCh3 || gb->audio.style != GB_AUDIO_DMG) {
gb->audio.ch3.wavedata8[address - GB_REG_WAVE_0] = value;
} else if(gb->audio.ch3.readable) {
@ -607,6 +608,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
case GB_REG_WAVE_E:
case GB_REG_WAVE_F:
if (gb->audio.playingCh3) {
GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0x4);
if (gb->audio.ch3.readable || gb->audio.style != GB_AUDIO_DMG) {
return gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1];
} else {
@ -620,6 +622,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
if (gb->model < GB_MODEL_CGB) {
mLOG(GB_IO, GAME_ERROR, "Reading from CGB register FF%02X in DMG mode", address);
} else if (gb->audio.enable) {
GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0x3);
return (gb->audio.ch1.sample) | (gb->audio.ch2.sample << 4);
}
break;
@ -627,7 +630,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
if (gb->model < GB_MODEL_CGB) {
mLOG(GB_IO, GAME_ERROR, "Reading from CGB register FF%02X in DMG mode", address);
} else if (gb->audio.enable) {
GBAudioUpdateChannel4(&gb->audio);
GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0xC);
return (gb->audio.ch3.sample) | (gb->audio.ch4.sample << 4);
}
break;

View File

@ -14,7 +14,7 @@
mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate", "gb.serialize");
MGBA_EXPORT const uint32_t GBSavestateMagic = 0x00400000;
MGBA_EXPORT const uint32_t GBSavestateVersion = 0x00000002;
MGBA_EXPORT const uint32_t GBSavestateVersion = 0x00000003;
void GBSerialize(struct GB* gb, struct GBSerializedState* state) {
STORE_32LE(GBSavestateMagic + GBSavestateVersion, 0, &state->versionMagic);

View File

@ -25,6 +25,7 @@ mLOG_DEFINE_CATEGORY(GBA_AUDIO, "GBA Audio", "gba.audio");
const unsigned GBA_AUDIO_SAMPLES = 2048;
const int GBA_AUDIO_VOLUME_MAX = 0x100;
static const int SAMPLE_INTERVAL = GBA_ARM7TDMI_FREQUENCY / 0x4000;
static const int CLOCKS_PER_FRAME = 0x800;
static int _applyBias(struct GBAAudio* audio, int sample);
@ -66,13 +67,16 @@ void GBAAudioReset(struct GBAAudio* audio) {
audio->chA.internalSample = 0;
audio->chA.internalRemaining = 0;
memset(audio->chA.fifo, 0, sizeof(audio->chA.fifo));
audio->chA.sample = 0;
audio->chB.fifoWrite = 0;
audio->chB.fifoRead = 0;
audio->chB.internalSample = 0;
audio->chB.internalRemaining = 0;
memset(audio->chB.fifo, 0, sizeof(audio->chB.fifo));
audio->chB.sample = 0;
int i;
for (i = 0; i < 8; ++i) {
audio->chA.samples[i] = 0;
audio->chB.samples[i] = 0;
}
audio->sampleRate = 0x8000;
audio->soundbias = 0x200;
audio->volume = 0;
@ -218,6 +222,7 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) {
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) {
audio->soundbias = value;
audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value);
}
void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) {
@ -229,6 +234,7 @@ void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) {
bank = 1;
}
GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing), 0x4);
audio->psg.ch3.wavedata32[address | (bank * 4)] = value;
}
@ -241,6 +247,7 @@ uint32_t GBAAudioReadWaveRAM(struct GBAAudio* audio, int address) {
bank = 1;
}
GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing), 0x4);
return audio->psg.ch3.wavedata32[address | (bank * 4)];
}
@ -297,7 +304,14 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
channel->fifoRead = 0;
}
}
channel->sample = channel->internalSample;
int32_t until = mTimingUntil(&audio->p->timing, &audio->sampleEvent) - 1;
int bits = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
until += 1 << (9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias));
until >>= 9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias);
int i;
for (i = bits - until; i < bits; ++i) {
channel->samples[i] = channel->internalSample;
}
if (channel->internalRemaining) {
channel->internalSample >>= 8;
--channel->internalRemaining;
@ -316,9 +330,15 @@ static int _applyBias(struct GBAAudio* audio, int sample) {
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;
int sample;
for (sample = 0; sample * audio->sampleInterval < (int32_t) SAMPLE_INTERVAL; ++sample) {
int16_t sampleLeft = 0;
int16_t sampleRight = 0;
int psgShift = 4 - audio->volume;
GBAudioRun(&audio->psg, timestamp + (sample + 1) * audio->sampleInterval, 0xF);
GBAudioSamplePSG(&audio->psg, &sampleLeft, &sampleRight);
sampleLeft >>= psgShift;
sampleRight >>= psgShift;
@ -329,30 +349,44 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
if (!audio->externalMixing) {
if (!audio->forceDisableChA) {
if (audio->chALeft) {
sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA;
sampleLeft += (audio->chA.samples[sample] << 2) >> !audio->volumeChA;
}
if (audio->chARight) {
sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA;
sampleRight += (audio->chA.samples[sample] << 2) >> !audio->volumeChA;
}
}
if (!audio->forceDisableChB) {
if (audio->chBLeft) {
sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB;
sampleLeft += (audio->chB.samples[sample] << 2) >> !audio->volumeChB;
}
if (audio->chBRight) {
sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
sampleRight += (audio->chB.samples[sample] << 2) >> !audio->volumeChB;
}
}
}
sampleLeft = _applyBias(audio, sampleLeft);
sampleRight = _applyBias(audio, sampleRight);
samplesLeft[sample] = sampleLeft;
samplesRight[sample] = 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));
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];
sampleSumLeft += sampleLeft;
sampleSumRight += sampleRight;
if ((size_t) blip_samples_avail(audio->psg.left) < audio->samples) {
blip_add_delta(audio->psg.left, audio->clock, sampleLeft - audio->lastLeft);
blip_add_delta(audio->psg.right, audio->clock, sampleRight - audio->lastRight);
@ -365,10 +399,14 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
audio->clock -= CLOCKS_PER_FRAME;
}
}
produced = blip_samples_avail(audio->psg.left);
if (audio->p->stream && audio->p->stream->postAudioFrame) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
}
// 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);
}
produced = blip_samples_avail(audio->psg.left);
bool wait = produced >= audio->samples;
if (!mCoreSyncProduceAudio(audio->p->sync, audio->psg.left, audio->samples)) {
// Interrupted
@ -379,7 +417,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
audio->p->stream->postAudioBuffer(audio->p->stream, audio->psg.left, audio->psg.right);
}
mTimingSchedule(timing, &audio->sampleEvent, audio->sampleInterval - cyclesLate);
mTimingSchedule(timing, &audio->sampleEvent, SAMPLE_INTERVAL - cyclesLate);
}
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
@ -387,8 +425,8 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState*
STORE_32(audio->chA.internalSample, 0, &state->audio.internalA);
STORE_32(audio->chB.internalSample, 0, &state->audio.internalB);
state->audio.sampleA = audio->chA.sample;
state->audio.sampleB = audio->chB.sample;
memcpy(state->samples.chA, audio->chA.samples, sizeof(audio->chA.samples));
memcpy(state->samples.chB, audio->chB.samples, sizeof(audio->chB.samples));
int readA = audio->chA.fifoRead;
int readB = audio->chB.fifoRead;
@ -434,8 +472,8 @@ void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState
LOAD_32(audio->chA.internalSample, 0, &state->audio.internalA);
LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB);
audio->chA.sample = state->audio.sampleA;
audio->chB.sample = state->audio.sampleB;
memcpy(audio->chA.samples, state->samples.chA, sizeof(audio->chA.samples));
memcpy(audio->chB.samples, state->samples.chB, sizeof(audio->chB.samples));
int readA = 0;
int readB = 0;

View File

@ -416,6 +416,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
value |= gba->memory.io[REG_SOUNDCNT_X >> 1] & 0xF;
break;
case REG_SOUNDBIAS:
value &= 0xC3FE;
GBAAudioWriteSOUNDBIAS(&gba->audio, value);
break;
@ -842,7 +843,6 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
gba->memory.io[REG_JOYSTAT >> 1] &= ~JOYSTAT_RECV;
break;
case REG_SOUNDBIAS:
case REG_POSTFLG:
mLOG(GBA_IO, STUB, "Stub I/O register read: %03x", address);
break;
@ -897,6 +897,7 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
case REG_BLDALPHA:
case REG_SOUNDCNT_HI:
case REG_SOUNDCNT_X:
case REG_SOUNDBIAS:
case REG_DMA0CNT_HI:
case REG_DMA1CNT_HI:
case REG_DMA2CNT_HI:

View File

@ -1239,9 +1239,13 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o
if ((address & 0x0001FFFF) < SIZE_VRAM) {
LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram);
STORE_32(value, address & 0x0001FFFC, gba->video.vram);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFC);
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) | 2);
} else {
LOAD_32(oldValue, address & 0x00017FFC, gba->video.vram);
STORE_32(value, address & 0x00017FFC, gba->video.vram);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFC);
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) | 2);
}
break;
case REGION_OAM:
@ -1308,9 +1312,11 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o
if ((address & 0x0001FFFF) < SIZE_VRAM) {
LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram);
STORE_16(value, address & 0x0001FFFE, gba->video.vram);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
} else {
LOAD_16(oldValue, address & 0x00017FFE, gba->video.vram);
STORE_16(value, address & 0x00017FFE, gba->video.vram);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE);
}
break;
case REGION_OAM:

View File

@ -70,6 +70,9 @@ static const struct GBACartridgeOverride _overrides[] = {
{ "AI2E", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false },
{ "AI2P", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false },
// Game Boy Wars Advance 1+2
{ "BGWJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
// Golden Sun: The Lost Age
{ "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A, false },

View File

@ -15,7 +15,7 @@
#include <fcntl.h>
MGBA_EXPORT const uint32_t GBASavestateMagic = 0x01000000;
MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000004;
MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000005;
mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize");

View File

@ -24,13 +24,23 @@ ApplicationUpdatePrompt::ApplicationUpdatePrompt(QWidget* parent)
ApplicationUpdater* updater = GBAApp::app()->updater();
ApplicationUpdater::UpdateInfo info = updater->updateInfo();
QString updateText(tr("An update to %1 is available.\n").arg(QLatin1String(projectName)));
bool available;
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
available = true;
#elif defined(Q_OS_LINUX)
QString path = QCoreApplication::applicationDirPath();
QFileInfo finfo(path + "/../../AppRun");
available = finfo.exists() && finfo.isExecutable();
#else
available = false;
#endif
if (available) {
updateText += tr("\nDo you want to download and install it now? You will need to restart the emulator when the download is complete.");
m_okDownload = connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &ApplicationUpdatePrompt::startUpdate);
#else
} else {
updateText += tr("\nAuto-update is not available on this platform. If you wish to update you will need to do it manually.");
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &QWidget::close);
#endif
}
m_ui.text->setText(updateText);
m_ui.details->setText(tr("Current version: %1\nNew version: %2\nDownload size: %3")
.arg(QLatin1String(projectVersion))

View File

@ -151,6 +151,8 @@ const char* ApplicationUpdater::platform() {
#endif
#elif defined(Q_OS_MACOS)
return "osx";
#elif defined(Q_OS_LINUX) && defined(__x86_64__)
return "appimage-x64";
#else
// Return one that will be up to date, but we can't download
return "win64";

View File

@ -268,7 +268,14 @@ else()
endif()
if(BUILD_UPDATER)
if(DEFINED CMAKE_CONFIGURATION_TYPES)
# Required for e.g. MSVC
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc INPUT ${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in)
else()
# Required for qt_add_resources to manage dependencies properly
# TODO: Figure out how to do this with MSVC too
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/updater-config.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc)
endif()
if(TARGET Qt6::Core)
qt_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc)
else()

View File

@ -206,6 +206,8 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
QMetaObject::invokeMethod(m_painter.get(), "start");
if (!m_gl) {
setUpdatesEnabled(false);
} else {
show();
}
}
@ -268,6 +270,9 @@ void DisplayGL::stopDrawing() {
m_hasStarted = false;
CoreController::Interrupter interrupter(m_context);
QMetaObject::invokeMethod(m_painter.get(), "stop", Qt::BlockingQueuedConnection);
if (m_gl) {
hide();
}
setUpdatesEnabled(true);
}
m_context.reset();
@ -277,9 +282,7 @@ void DisplayGL::pauseDrawing() {
if (m_hasStarted) {
m_isDrawing = false;
QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
#ifndef Q_OS_MAC
setUpdatesEnabled(true);
#endif
}
}
@ -287,11 +290,9 @@ void DisplayGL::unpauseDrawing() {
if (m_hasStarted) {
m_isDrawing = true;
QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
#ifndef Q_OS_MAC
if (!m_gl) {
setUpdatesEnabled(false);
}
#endif
}
}

View File

@ -0,0 +1,5 @@
<RCC>
<qresource>
<file alias="updater">${PROJECT_BINARY_DIR}/updater-stub${CMAKE_EXECUTABLE_SUFFIX}</file>
</qresource>
</RCC>

View File

@ -10,7 +10,9 @@ set(TEST_FILES
if(USE_LUA)
list(APPEND SOURCE_FILES engines/lua.c)
list(APPEND TEST_FILES test/lua.c)
list(APPEND TEST_FILES
test/stdlib.c
test/lua.c)
endif()
source_group("Scripting" FILES ${SOURCE_FILES})

View File

@ -57,6 +57,7 @@ void mScriptContextInit(struct mScriptContext* context) {
context->nextWeakref = 1;
HashTableInit(&context->callbacks, 0, (void (*)(void*)) mScriptValueDeref);
context->constants = NULL;
HashTableInit(&context->docstrings, 0, NULL);
}
void mScriptContextDeinit(struct mScriptContext* context) {
@ -66,6 +67,7 @@ void mScriptContextDeinit(struct mScriptContext* context) {
mScriptListDeinit(&context->refPool);
HashTableDeinit(&context->callbacks);
HashTableDeinit(&context->engines);
HashTableDeinit(&context->docstrings);
}
void mScriptContextFillPool(struct mScriptContext* context, struct mScriptValue* value) {
@ -237,6 +239,26 @@ void mScriptContextExportConstants(struct mScriptContext* context, const char* n
mScriptValueDeref(table);
}
void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* values) {
struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
size_t i;
for (i = 0; values[i].key; ++i) {
struct mScriptValue* key = mScriptStringCreateFromUTF8(values[i].key);
mScriptTableInsert(table, key, values[i].value);
mScriptValueDeref(key);
mScriptValueDeref(values[i].value);
}
mScriptContextSetGlobal(context, nspace, table);
}
void mScriptContextSetDocstring(struct mScriptContext* context, const char* key, const char* docstring) {
HashTableInsert(&context->docstrings, key, docstring);
}
const char* mScriptContextGetDocstring(struct mScriptContext* context, const char* key) {
return HashTableLookup(&context->docstrings, key);
}
bool mScriptContextLoadVF(struct mScriptContext* context, const char* name, struct VFile* vf) {
struct mScriptFileInfo info = {
.name = name,

View File

@ -12,7 +12,7 @@ struct mScriptContext context;
struct Table types;
FILE* out;
void explainValue(struct mScriptValue* value, int level);
void explainValue(struct mScriptValue* value, const char* name, int level);
void explainType(struct mScriptType* type, int level);
void addTypesFromTuple(const struct mScriptTypeTuple*);
@ -154,7 +154,7 @@ bool printval(const struct mScriptValue* value, char* buffer, size_t bufferSize)
return false;
}
void explainTable(struct mScriptValue* value, int level) {
void explainTable(struct mScriptValue* value, const char* name, int level) {
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
@ -167,7 +167,14 @@ void explainTable(struct mScriptValue* value, int level) {
printval(k, keyval, sizeof(keyval));
fprintf(out, "%s- key: %s\n", indent, keyval);
struct mScriptValue* v = mScriptTableIteratorGetValue(value, &iter);
explainValue(v, level + 1);
struct mScriptValue string;
if (mScriptCast(mSCRIPT_TYPE_MS_CHARP, k, &string)) {
snprintf(keyval, sizeof(keyval), "%s.%s", name, (const char*) string.value.opaque);
explainValue(v, keyval, level + 1);
} else {
explainValue(v, NULL, level + 1);
}
} while (mScriptTableIteratorNext(value, &iter));
}
}
@ -234,15 +241,15 @@ void explainObject(struct mScriptValue* value, int level) {
struct mScriptValue* unwrappedMember;
if (member.type->base == mSCRIPT_TYPE_WRAPPER) {
unwrappedMember = mScriptValueUnwrap(&member);
explainValue(unwrappedMember, level + 2);
explainValue(unwrappedMember, NULL, level + 2);
} else {
explainValue(&member, level + 2);
explainValue(&member, NULL, level + 2);
}
}
}
}
void explainValue(struct mScriptValue* value, int level) {
void explainValue(struct mScriptValue* value, const char* name, int level) {
char valstring[1024];
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
@ -250,10 +257,19 @@ void explainValue(struct mScriptValue* value, int level) {
value = mScriptContextAccessWeakref(&context, value);
addType(value->type);
fprintf(out, "%stype: %s\n", indent, value->type->name);
const char* docstring = NULL;
if (name) {
docstring = mScriptContextGetDocstring(&context, name);
}
if (docstring) {
fprintf(out, "%scomment: \"%s\"\n", indent, docstring);
}
switch (value->type->base) {
case mSCRIPT_TYPE_TABLE:
fprintf(out, "%svalue:\n", indent);
explainTable(value, level);
explainTable(value, name, level);
break;
case mSCRIPT_TYPE_SINT:
case mSCRIPT_TYPE_UINT:
@ -462,7 +478,7 @@ int main(int argc, char* argv[]) {
const char* name = HashTableIteratorGetKey(&context.rootScope, &iter);
fprintf(out, " %s:\n", name);
struct mScriptValue* value = HashTableIteratorGetValue(&context.rootScope, &iter);
explainValue(value, 1);
explainValue(value, name, 1);
} while (HashTableIteratorNext(&context.rootScope, &iter));
}
fputs("emu:\n", out);

View File

@ -30,7 +30,7 @@ static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*,
static bool _luaPopFrame(struct mScriptEngineContextLua*, struct mScriptList*);
static bool _luaInvoke(struct mScriptEngineContextLua*, struct mScriptFrame*);
static struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext);
static struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool pop);
static bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue*);
static void _luaDeref(struct mScriptValue*);
@ -106,6 +106,7 @@ static const luaL_Reg _mSTTable[] = {
{ "__index", _luaGetTable },
{ "__len", _luaLenTable },
{ "__pairs", _luaPairsTable },
{ "__gc", _luaGcObject },
{ NULL, NULL }
};
@ -186,7 +187,7 @@ bool _luaIsScript(struct mScriptEngineContext* ctx, const char* name, struct VFi
struct mScriptValue* _luaGetGlobal(struct mScriptEngineContext* ctx, const char* name) {
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) ctx;
lua_getglobal(luaContext->lua, name);
return _luaCoerce(luaContext);
return _luaCoerce(luaContext, true);
}
bool _luaSetGlobal(struct mScriptEngineContext* ctx, const char* name, struct mScriptValue* value) {
@ -212,7 +213,83 @@ struct mScriptValue* _luaCoerceFunction(struct mScriptEngineContextLua* luaConte
return value;
}
struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext) {
struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) {
struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
bool isList = true;
lua_pushnil(luaContext->lua);
while (lua_next(luaContext->lua, -2) != 0) {
struct mScriptValue* value = NULL;
int type = lua_type(luaContext->lua, -1);
switch (type) {
case LUA_TNUMBER:
case LUA_TBOOLEAN:
case LUA_TSTRING:
case LUA_TFUNCTION:
value = _luaCoerce(luaContext, true);
break;
default:
// Don't let values be something that could contain themselves
break;
}
if (!value) {
lua_pop(luaContext->lua, 3);
mScriptValueDeref(table);
return false;
}
struct mScriptValue* key = NULL;
type = lua_type(luaContext->lua, -1);
switch (type) {
case LUA_TBOOLEAN:
case LUA_TSTRING:
isList = false;
// Fall through
case LUA_TNUMBER:
key = _luaCoerce(luaContext, false);
break;
default:
// Limit keys to hashable types
break;
}
if (!key) {
lua_pop(luaContext->lua, 2);
mScriptValueDeref(table);
return false;
}
mScriptTableInsert(table, key, value);
mScriptValueDeref(key);
mScriptValueDeref(value);
}
lua_pop(luaContext->lua, 1);
size_t len = mScriptTableSize(table);
if (!isList || !len) {
return table;
}
struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST);
size_t i;
for (i = 1; i <= len; ++i) {
struct mScriptValue* value = mScriptTableLookup(table, &mSCRIPT_MAKE_S64(i));
if (!value) {
mScriptValueDeref(list);
return table;
}
mScriptValueWrap(value, mScriptListAppend(list->value.list));
}
if (i != len + 1) {
mScriptValueDeref(list);
mScriptContextFillPool(luaContext->d.context, table);
return table;
}
mScriptValueDeref(table);
mScriptContextFillPool(luaContext->d.context, list);
return list;
}
struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool pop) {
if (lua_isnone(luaContext->lua, -1)) {
lua_pop(luaContext->lua, 1);
return NULL;
@ -244,7 +321,16 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext) {
break;
case LUA_TFUNCTION:
// This function pops the value internally via luaL_ref
if (!pop) {
break;
}
return _luaCoerceFunction(luaContext);
case LUA_TTABLE:
// This function pops the value internally via luaL_ref
if (!pop) {
break;
}
return _luaCoerceTable(luaContext);
case LUA_TUSERDATA:
if (!lua_getmetatable(luaContext->lua, -1)) {
break;
@ -259,7 +345,9 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext) {
value = mScriptContextAccessWeakref(luaContext->d.context, value);
break;
}
if (pop) {
lua_pop(luaContext->lua, 1);
}
return value;
}
@ -325,7 +413,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v
*newValue = mSCRIPT_MAKE(WEAKREF, weakref);
} else {
mScriptValueWrap(value, newValue);
mScriptValueDeref(value);
}
luaL_setmetatable(luaContext->lua, "mSTList");
break;
@ -335,7 +422,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v
*newValue = mSCRIPT_MAKE(WEAKREF, weakref);
} else {
mScriptValueWrap(value, newValue);
mScriptValueDeref(value);
}
luaL_setmetatable(luaContext->lua, "mSTTable");
break;
@ -353,7 +439,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v
*newValue = mSCRIPT_MAKE(WEAKREF, weakref);
} else {
mScriptValueWrap(value, newValue);
mScriptValueDeref(value);
}
luaL_setmetatable(luaContext->lua, "mSTStruct");
break;
@ -467,7 +552,7 @@ bool _luaPopFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList
if (frame) {
int i;
for (i = 0; i < count; ++i) {
struct mScriptValue* value = _luaCoerce(luaContext);
struct mScriptValue* value = _luaCoerce(luaContext, true);
if (!value) {
ok = false;
break;
@ -640,7 +725,7 @@ int _luaSetObject(lua_State* lua) {
char key[MAX_KEY_SIZE];
const char* keyPtr = lua_tostring(lua, -2);
struct mScriptValue* obj = lua_touserdata(lua, -3);
struct mScriptValue* val = _luaCoerce(luaContext);
struct mScriptValue* val = _luaCoerce(luaContext, true);
if (!keyPtr) {
lua_pop(lua, 2);

View File

@ -33,6 +33,38 @@ static void _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, struct m
mSCRIPT_DECLARE_STRUCT(mScriptCallbackManager);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCallbackManager, add, _mScriptCallbackAdd, 2, STR, callback, WRAPPER, function);
static uint64_t mScriptMakeBitmask(struct mScriptList* list) {
size_t i;
uint64_t mask = 0;
for (i = 0; i < mScriptListSize(list); ++i) {
struct mScriptValue bit;
struct mScriptValue* value = mScriptListGetPointer(list, i);
if (value->type->base == mSCRIPT_TYPE_WRAPPER) {
value = mScriptValueUnwrap(value);
}
if (!mScriptCast(mSCRIPT_TYPE_MS_U64, value, &bit)) {
continue;
}
mask |= 1ULL << bit.value.u64;
}
return mask;
}
static struct mScriptValue* mScriptExpandBitmask(uint64_t mask) {
struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST);
size_t i;
for (i = 0; mask; ++i, mask >>= 1) {
if (!(mask & 1)) {
continue;
}
*mScriptListAppend(list->value.list) = mSCRIPT_MAKE_U32(i);
}
return list;
}
mSCRIPT_BIND_FUNCTION(mScriptMakeBitmask_Binding, U64, mScriptMakeBitmask, 1, LIST, bits);
mSCRIPT_BIND_FUNCTION(mScriptExpandBitmask_Binding, WLIST, mScriptExpandBitmask, 1, U64, mask);
mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
"A global singleton object `callbacks` used for managing callbacks. The following callbacks are defined:\n\n"
@ -61,6 +93,7 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) {
};
lib->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
mScriptContextSetGlobal(context, "callbacks", lib);
mScriptContextSetDocstring(context, "callbacks", "Singleton instance of struct::mScriptCallbackManager");
mScriptContextExportConstants(context, "SAVESTATE", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(SAVESTATE, SCREENSHOT),
@ -69,18 +102,18 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) {
mSCRIPT_CONSTANT_PAIR(SAVESTATE, RTC),
mSCRIPT_CONSTANT_PAIR(SAVESTATE, METADATA),
mSCRIPT_CONSTANT_PAIR(SAVESTATE, ALL),
mSCRIPT_CONSTANT_SENTINEL
mSCRIPT_KV_SENTINEL
});
mScriptContextExportConstants(context, "PLATFORM", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(mPLATFORM, NONE),
mSCRIPT_CONSTANT_PAIR(mPLATFORM, GBA),
mSCRIPT_CONSTANT_PAIR(mPLATFORM, GB),
mSCRIPT_CONSTANT_PAIR(mPLATFORM, DS),
mSCRIPT_CONSTANT_SENTINEL
mSCRIPT_KV_SENTINEL
});
mScriptContextExportConstants(context, "CHECKSUM", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(mCHECKSUM, CRC32),
mSCRIPT_CONSTANT_SENTINEL
mSCRIPT_KV_SENTINEL
});
#ifdef M_CORE_GBA
mScriptContextExportConstants(context, "GBA_KEY", (struct mScriptKVPair[]) {
@ -94,7 +127,7 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) {
mSCRIPT_CONSTANT_PAIR(GBA_KEY, DOWN),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, R),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, L),
mSCRIPT_CONSTANT_SENTINEL
mSCRIPT_KV_SENTINEL
});
#endif
#ifdef M_CORE_GB
@ -107,7 +140,7 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) {
mSCRIPT_CONSTANT_PAIR(GB_KEY, LEFT),
mSCRIPT_CONSTANT_PAIR(GB_KEY, UP),
mSCRIPT_CONSTANT_PAIR(GB_KEY, DOWN),
mSCRIPT_CONSTANT_SENTINEL
mSCRIPT_KV_SENTINEL
});
#endif
#ifdef M_CORE_DS
@ -124,8 +157,18 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) {
mSCRIPT_CONSTANT_PAIR(DS_KEY, L),
mSCRIPT_CONSTANT_PAIR(DS_KEY, X),
mSCRIPT_CONSTANT_PAIR(DS_KEY, Y),
mSCRIPT_CONSTANT_SENTINEL
mSCRIPT_KV_SENTINEL
});
#endif
mScriptContextSetGlobal(context, "C", context->constants);
mScriptContextSetDocstring(context, "C", "A table containing the [exported constants](#constants)");
mScriptContextExportNamespace(context, "util", (struct mScriptKVPair[]) {
mSCRIPT_KV_PAIR(makeBitmask, &mScriptMakeBitmask_Binding),
mSCRIPT_KV_PAIR(expandBitmask, &mScriptExpandBitmask_Binding),
mSCRIPT_KV_SENTINEL
});
mScriptContextSetDocstring(context, "util", "Basic utility library");
mScriptContextSetDocstring(context, "util.makeBitmask", "Compile a list of bit indices into a bitmask");
mScriptContextSetDocstring(context, "util.expandBitmask", "Expand a bitmask into a list of bit indices");
}

View File

@ -61,8 +61,22 @@ static void testV1(struct Test* a, int b) {
a->i += b;
}
static int32_t sum(struct mScriptList* list) {
int32_t sum = 0;
size_t i;
for (i = 0; i < mScriptListSize(list); ++i) {
struct mScriptValue value;
if (!mScriptCast(mSCRIPT_TYPE_MS_S32, mScriptListGetPointer(list, i), &value)) {
continue;
}
sum += value.value.s32;
}
return sum;
}
mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, a);
mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b);
mSCRIPT_BIND_FUNCTION(boundSum, S32, sum, 1, LIST, list);
mSCRIPT_DECLARE_STRUCT(Test);
mSCRIPT_DECLARE_STRUCT_D_METHOD(Test, S32, ifn0, 0);
@ -619,6 +633,24 @@ M_TEST_DEFINE(tableIterate) {
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(callList) {
SETUP_LUA;
struct mScriptValue a = mSCRIPT_MAKE_S32(6);
struct mScriptValue* val;
assert_true(lua->setGlobal(lua, "sum", &boundSum));
TEST_PROGRAM("a = sum({1, 2, 3})");
assert_null(lua->getError(lua));
val = lua->getGlobal(lua, "a");
assert_non_null(val);
assert_true(mSCRIPT_TYPE_MS_S32->equal(&a, val));
mScriptValueDeref(val);
mScriptContextDeinit(&context);
}
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua,
cmocka_unit_test(create),
cmocka_unit_test(loadGood),
@ -634,4 +666,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua,
cmocka_unit_test(errorReporting),
cmocka_unit_test(tableLookup),
cmocka_unit_test(tableIterate),
cmocka_unit_test(callList),
)

91
src/script/test/stdlib.c Normal file
View File

@ -0,0 +1,91 @@
/* Copyright (c) 2013-2022 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "util/test/suite.h"
#include <mgba/internal/script/lua.h>
#include <mgba/script/context.h>
#include <mgba/script/types.h>
#define SETUP_LUA \
struct mScriptContext context; \
mScriptContextInit(&context); \
struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \
mScriptContextAttachStdlib(&context)
#define LOAD_PROGRAM(PROG) \
do { \
struct VFile* vf = VFileFromConstMemory(PROG, strlen(PROG)); \
assert_true(lua->load(lua, NULL, vf)); \
vf->close(vf); \
} while(0)
#define TEST_PROGRAM(PROG) \
LOAD_PROGRAM(PROG); \
assert_true(lua->run(lua)); \
#define TEST_VALUE(TYPE, NAME, VALUE) \
do { \
struct mScriptValue val = mSCRIPT_MAKE(TYPE, VALUE); \
struct mScriptValue* global = lua->getGlobal(lua, NAME); \
assert_non_null(global); \
assert_true(global->type->equal(global, &val)); \
mScriptValueDeref(global); \
} while(0)
M_TEST_SUITE_SETUP(mScriptStdlib) {
if (mSCRIPT_ENGINE_LUA->init) {
mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_SUITE_TEARDOWN(mScriptStdlib) {
if (mSCRIPT_ENGINE_LUA->deinit) {
mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_DEFINE(bitMask) {
SETUP_LUA;
TEST_PROGRAM("assert(util)");
TEST_PROGRAM("assert(util.makeBitmask)");
TEST_PROGRAM("assert(util.makeBitmask{0} == 1)");
TEST_PROGRAM("assert(util.makeBitmask{1} == 2)");
TEST_PROGRAM("assert(util.makeBitmask{0, 1} == 3)");
TEST_PROGRAM("assert(util.makeBitmask{1, 1} == 2)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(bitUnmask) {
SETUP_LUA;
TEST_PROGRAM("assert(util)");
TEST_PROGRAM("assert(util.expandBitmask)");
TEST_PROGRAM("assert(#util.expandBitmask(0) == 0)");
TEST_PROGRAM("assert(#util.expandBitmask(1) == 1)");
TEST_PROGRAM("assert(util.expandBitmask(1)[1] == 0)");
TEST_PROGRAM("assert(#util.expandBitmask(2) == 1)");
TEST_PROGRAM("assert(util.expandBitmask(2)[1] == 1)");
TEST_PROGRAM("assert(#util.expandBitmask(3) == 2)");
TEST_PROGRAM("assert(util.expandBitmask(3)[1] == 0 or util.expandBitmask(3)[1] == 1)");
TEST_PROGRAM("assert(util.expandBitmask(3)[2] == 0 or util.expandBitmask(3)[2] == 1)");
TEST_PROGRAM("assert(#util.expandBitmask(6) == 2)");
TEST_PROGRAM("assert(util.expandBitmask(6)[1] == 1 or util.expandBitmask(6)[1] == 2)");
TEST_PROGRAM("assert(util.expandBitmask(6)[2] == 1 or util.expandBitmask(6)[2] == 2)");
TEST_PROGRAM("assert(#util.expandBitmask(7) == 3)");
TEST_PROGRAM("assert(#util.expandBitmask(11) == 3)");
TEST_PROGRAM("assert(#util.expandBitmask(15) == 4)");
mScriptContextDeinit(&context);
}
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStdlib,
cmocka_unit_test(bitMask),
cmocka_unit_test(bitUnmask),
)

View File

@ -52,6 +52,30 @@ static int isHello(const char* str) {
return strcmp(str, "hello") == 0;
}
static int isSequential(struct mScriptList* list) {
int last;
if (mScriptListSize(list) == 0) {
return true;
}
size_t i;
for (i = 0; i < mScriptListSize(list); ++i) {
struct mScriptValue* value = mScriptListGetPointer(list, i);
struct mScriptValue intValue;
if (!mScriptCast(mSCRIPT_TYPE_MS_S32, value, &intValue)) {
return false;
}
if (!i) {
last = intValue.value.s32;
} else {
if (intValue.value.s32 != last + 1) {
return false;
}
++last;
}
}
return true;
}
mSCRIPT_BIND_FUNCTION(boundVoidOne, S32, voidOne, 0);
mSCRIPT_BIND_VOID_FUNCTION(boundDiscard, discard, 1, S32, ignored);
mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, in);
@ -61,6 +85,7 @@ mSCRIPT_BIND_FUNCTION(boundIdentityStruct, S(Test), identityStruct, 1, S(Test),
mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b);
mSCRIPT_BIND_FUNCTION(boundSubInts, S32, subInts, 2, S32, a, S32, b);
mSCRIPT_BIND_FUNCTION(boundIsHello, S32, isHello, 1, CHARP, str);
mSCRIPT_BIND_FUNCTION(boundIsSequential, S32, isSequential, 1, LIST, list);
M_TEST_DEFINE(voidArgs) {
struct mScriptFrame frame;
@ -919,6 +944,47 @@ M_TEST_DEFINE(stringIsNotHello) {
mScriptFrameDeinit(&frame);
}
M_TEST_DEFINE(invokeList) {
struct mScriptFrame frame;
struct mScriptList list;
int val;
mScriptListInit(&list, 0);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, LIST, &list);
assert_true(mScriptInvoke(&boundIsSequential, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &val));
assert_int_equal(val, 1);
mScriptFrameDeinit(&frame);
*mScriptListAppend(&list) = mSCRIPT_MAKE_S32(1);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, LIST, &list);
assert_true(mScriptInvoke(&boundIsSequential, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &val));
assert_int_equal(val, 1);
mScriptFrameDeinit(&frame);
*mScriptListAppend(&list) = mSCRIPT_MAKE_S32(2);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, LIST, &list);
assert_true(mScriptInvoke(&boundIsSequential, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &val));
assert_int_equal(val, 1);
mScriptFrameDeinit(&frame);
*mScriptListAppend(&list) = mSCRIPT_MAKE_S32(4);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, LIST, &list);
assert_true(mScriptInvoke(&boundIsSequential, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &val));
assert_int_equal(val, 0);
mScriptFrameDeinit(&frame);
mScriptListDeinit(&list);
}
M_TEST_SUITE_DEFINE(mScript,
cmocka_unit_test(voidArgs),
cmocka_unit_test(voidFunc),
@ -948,4 +1014,6 @@ M_TEST_SUITE_DEFINE(mScript,
cmocka_unit_test(hashTableBasic),
cmocka_unit_test(hashTableString),
cmocka_unit_test(stringIsHello),
cmocka_unit_test(stringIsNotHello))
cmocka_unit_test(stringIsNotHello),
cmocka_unit_test(invokeList),
)

View File

@ -221,6 +221,15 @@ const struct mScriptType mSTStringWrapper = {
.hash = NULL,
};
const struct mScriptType mSTListWrapper = {
.base = mSCRIPT_TYPE_WRAPPER,
.size = sizeof(struct mScriptValue),
.name = "wrapper list",
.alloc = NULL,
.free = NULL,
.hash = NULL,
};
const struct mScriptType mSTWeakref = {
.base = mSCRIPT_TYPE_WEAKREF,
.size = sizeof(uint32_t),