mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into medusa
This commit is contained in:
commit
d693a90023
3
CHANGES
3
CHANGES
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
404
src/gb/audio.c
404
src/gb/audio.c
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
130
src/gba/audio.c
130
src/gba/audio.c
|
@ -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,59 +330,83 @@ 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 sampleLeft = 0;
|
||||
int16_t sampleRight = 0;
|
||||
int psgShift = 4 - audio->volume;
|
||||
GBAudioSamplePSG(&audio->psg, &sampleLeft, &sampleRight);
|
||||
sampleLeft >>= psgShift;
|
||||
sampleRight >>= psgShift;
|
||||
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;
|
||||
|
||||
if (audio->mixer) {
|
||||
audio->mixer->step(audio->mixer);
|
||||
}
|
||||
if (!audio->externalMixing) {
|
||||
if (!audio->forceDisableChA) {
|
||||
if (audio->chALeft) {
|
||||
sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA;
|
||||
if (audio->mixer) {
|
||||
audio->mixer->step(audio->mixer);
|
||||
}
|
||||
if (!audio->externalMixing) {
|
||||
if (!audio->forceDisableChA) {
|
||||
if (audio->chALeft) {
|
||||
sampleLeft += (audio->chA.samples[sample] << 2) >> !audio->volumeChA;
|
||||
}
|
||||
|
||||
if (audio->chARight) {
|
||||
sampleRight += (audio->chA.samples[sample] << 2) >> !audio->volumeChA;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->chARight) {
|
||||
sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA;
|
||||
if (!audio->forceDisableChB) {
|
||||
if (audio->chBLeft) {
|
||||
sampleLeft += (audio->chB.samples[sample] << 2) >> !audio->volumeChB;
|
||||
}
|
||||
|
||||
if (audio->chBRight) {
|
||||
sampleRight += (audio->chB.samples[sample] << 2) >> !audio->volumeChB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!audio->forceDisableChB) {
|
||||
if (audio->chBLeft) {
|
||||
sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB;
|
||||
}
|
||||
|
||||
if (audio->chBRight) {
|
||||
sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
|
||||
}
|
||||
}
|
||||
sampleLeft = _applyBias(audio, sampleLeft);
|
||||
sampleRight = _applyBias(audio, sampleRight);
|
||||
samplesLeft[sample] = sampleLeft;
|
||||
samplesRight[sample] = sampleRight;
|
||||
}
|
||||
|
||||
sampleLeft = _applyBias(audio, sampleLeft);
|
||||
sampleRight = _applyBias(audio, 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;
|
||||
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);
|
||||
audio->lastLeft = sampleLeft;
|
||||
audio->lastRight = sampleRight;
|
||||
audio->clock += audio->sampleInterval;
|
||||
if (audio->clock >= CLOCKS_PER_FRAME) {
|
||||
blip_end_frame(audio->psg.left, CLOCKS_PER_FRAME);
|
||||
blip_end_frame(audio->psg.right, CLOCKS_PER_FRAME);
|
||||
audio->clock -= CLOCKS_PER_FRAME;
|
||||
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);
|
||||
audio->lastLeft = sampleLeft;
|
||||
audio->lastRight = sampleRight;
|
||||
audio->clock += audio->sampleInterval;
|
||||
if (audio->clock >= CLOCKS_PER_FRAME) {
|
||||
blip_end_frame(audio->psg.left, CLOCKS_PER_FRAME);
|
||||
blip_end_frame(audio->psg.right, CLOCKS_PER_FRAME);
|
||||
audio->clock -= CLOCKS_PER_FRAME;
|
||||
}
|
||||
}
|
||||
}
|
||||
produced = blip_samples_avail(audio->psg.left);
|
||||
// TODO: Post all frames
|
||||
if (audio->p->stream && audio->p->stream->postAudioFrame) {
|
||||
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
|
||||
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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 },
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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)
|
||||
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);
|
||||
available = true;
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QString path = QCoreApplication::applicationDirPath();
|
||||
QFileInfo finfo(path + "/../../AppRun");
|
||||
available = finfo.exists() && finfo.isExecutable();
|
||||
#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);
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
m_ui.text->setText(updateText);
|
||||
m_ui.details->setText(tr("Current version: %1\nNew version: %2\nDownload size: %3")
|
||||
.arg(QLatin1String(projectVersion))
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -268,7 +268,14 @@ else()
|
|||
endif()
|
||||
|
||||
if(BUILD_UPDATER)
|
||||
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc INPUT ${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in)
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<RCC>
|
||||
<qresource>
|
||||
<file alias="updater">${PROJECT_BINARY_DIR}/updater-stub${CMAKE_EXECUTABLE_SUFFIX}</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -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})
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
lua_pop(luaContext->lua, 1);
|
||||
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);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* 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
|
||||
* 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 <mgba/script/context.h>
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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),
|
||||
)
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue