Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2023-02-15 01:31:47 -08:00
commit 47952e3413
45 changed files with 1522 additions and 117 deletions

11
CHANGES
View File

@ -60,6 +60,7 @@ Features:
Emulation fixes:
- ARM7: Fix unsigned multiply timing
- GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378)
- GB: Fix HALT breaking M-cycle alignment (fixes mgba.io/i/250)
- GB Audio: Fix channel 1/2 reseting edge cases (fixes mgba.io/i/1925)
- GB Audio: Properly apply per-model audio differences
- GB Audio: Revamp channel rendering
@ -73,6 +74,7 @@ Emulation fixes:
- GBA: Improve timing when not booting from BIOS
- GBA: Fix expected entry point for multiboot ELFs (fixes mgba.io/i/2450)
- GBA: Fix booting multiboot ROMs with no JOY entrypoint
- GBA: Fix 1 MiB ROM mirroring to only mirror 4 times
- GBA Audio: Adjust PSG sampling rate with SOUNDBIAS
- GBA Audio: Sample FIFOs at SOUNDBIAS-set frequency
- GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059)
@ -102,6 +104,7 @@ Other fixes:
- FFmpeg: Fix GIF recording (fixes mgba.io/i/2393)
- GB: Fix temporary saves
- GB: Fix replacing the ROM crashing when accessing ROM base
- GB: Don't try to map a 0-byte SRAM (fixes mgba.io/i/2668)
- GB, GBA: Save writeback-pending masked saves on unload (fixes mgba.io/i/2396)
- mGUI: Fix FPS counter after closing menu
- Qt: Fix some hangs when using the debugger console
@ -117,6 +120,7 @@ Misc:
- Debugger: GDB now works while the game is paused
- Debugger: Add command to load external symbol file (fixes mgba.io/i/2480)
- FFmpeg: Support dynamic audio sample rate
- GB: Support CGB0 boot ROM loading
- GB Audio: Increase sample rate
- GB MBC: Filter out MBC errors when cartridge is yanked (fixes mgba.io/i/2488)
- GB MBC: Partially implement TAMA5 RTC
@ -124,8 +128,9 @@ Misc:
- GBA: Automatically skip BIOS if ROM has invalid logo
- GBA: Refine multiboot detection (fixes mgba.io/i/2192)
- GBA Cheats: Implement "never" type codes (closes mgba.io/i/915)
- GBA Memory: Implement adjustable EWRAM waitstates (closes mgba.io/i/1276)
- GBA DMA: Enhanced logging (closes mgba.io/i/2454)
- GBA Memory: Implement adjustable EWRAM waitstates (closes mgba.io/i/1276)
- GBA Savedata: Store RTC data in savegames (closes mgba.io/i/240)
- GBA Video: Implement layer placement for OpenGL renderer (fixes mgba.io/i/1962)
- GBA Video: Fix highlighting for sprites with mid-frame palette changes
- mGUI: Add margin to right-aligned menu text (fixes mgba.io/i/871)
@ -145,8 +150,12 @@ Misc:
- Qt: Boot both a multiboot image and ROM with CLI args (closes mgba.io/i/1941)
- Qt: Improve cheat parsing (fixes mgba.io/i/2297)
- Qt: Change lossless setting to use WavPack audio
- Qt: Use FFmpeg to convert additional camera formats, if available
- Qt: Resume crashed game when loading a save state
- Qt: Include cheats in bug report
- SDL: Support exposing an axis directly as the gyro value (closes mgba.io/i/2531)
- Windows: Attach to console if present
- VFS: Early return NULL if attempting to map 0 bytes from a file
- Vita: Add bilinear filtering option (closes mgba.io/i/344)
0.9.3: (2021-12-17)

View File

@ -893,6 +893,10 @@ if(ENABLE_EXTRA)
list(APPEND SRC ${EXTRA_SRC})
endif()
if(ENABLE_SCRIPTING)
list(APPEND SRC ${CORE_SCRIPT_SRC})
endif()
if(NOT SKIP_LIBRARY)
if(NOT BUILD_STATIC AND NOT BUILD_SHARED)
set(BUILD_SHARED ON)

View File

@ -57,6 +57,17 @@
#define _mODD_8(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__))
#define _mODD_9(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__))
#define _mIF0_0(...) __VA_ARGS__
#define _mIF0_1(...)
#define _mIF0_2(...)
#define _mIF0_3(...)
#define _mIF0_4(...)
#define _mIF0_5(...)
#define _mIF0_6(...)
#define _mIF0_7(...)
#define _mIF0_8(...)
#define _mIF0_9(...)
#define _mSUCC_0 1
#define _mSUCC_1 2
#define _mSUCC_2 3

View File

@ -21,11 +21,14 @@ CXX_GUARD_START
typedef SOCKET Socket;
#else
#ifdef GEKKO
#define USE_GETHOSTBYNAME
#include <network.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#endif
#include <errno.h>
@ -191,6 +194,19 @@ static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress)
}
int err;
const int enable = 1;
#ifdef GEKKO
err = net_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
#elif defined(_WIN32)
err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*) &enable, sizeof(enable));
#else
err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
#endif
if (err) {
return INVALID_SOCKET;
}
if (!bindAddress) {
struct sockaddr_in bindInfo;
memset(&bindInfo, 0, sizeof(bindInfo));
@ -438,6 +454,73 @@ static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Soc
return result;
}
static inline int SocketResolveHost(const char* addrString, struct Address* destAddress) {
int result = 0;
#ifdef USE_GETHOSTBYNAME
#warning Using gethostbyname() for hostname resolution is not threadsafe
#ifdef GEKKO
struct hostent* host = net_gethostbyname(addrString);
#else
struct hostent* host = gethostbyname(addrString);
#endif
if (!host) {
return errno;
}
if (host->h_addrtype == AF_INET && host->h_length == 4) {
destAddress->version = IPV4;
destAddress->ipv4 = ntohl(*host->h_addr_list[0]);
}
#ifdef HAS_IPV6
else if (host->h_addrtype == AF_INET6 && host->h_length == 16) {
destAddress->version = IPV6;
memcpy(destAddress->ipv6, host->h_addr_list[0], 16);
}
#endif
else {
#ifdef GEKKO
result = errno;
#else
result = -h_errno;
#endif
}
#else
struct addrinfo* addr = NULL;
result = getaddrinfo(addrString, NULL, NULL, &addr);
if (result) {
#ifdef EAI_SYSTEM
if (result == EAI_SYSTEM) {
result = errno;
}
#endif
goto error;
}
if (addr->ai_family == AF_INET && addr->ai_addrlen == sizeof(struct sockaddr_in)) {
struct sockaddr_in* addr4 = (struct sockaddr_in*) addr->ai_addr;
destAddress->version = IPV4;
destAddress->ipv4 = ntohl(addr4->sin_addr.s_addr);
}
#ifdef HAS_IPV6
else if (addr->ai_family == AF_INET6 && addr->ai_addrlen == sizeof(struct sockaddr_in6)) {
struct sockaddr_in6* addr6 = (struct sockaddr_in6*) addr->ai_addr;
destAddress->version = IPV6;
memcpy(destAddress->ipv6, addr6->sin6_addr.s6_addr, 16);
}
#endif
else {
#ifdef _WIN32
result = WSANO_DATA;
#else
result = EAI_NONAME;
#endif
}
error:
if (addr) {
freeaddrinfo(addr);
}
#endif
return result;
}
CXX_GUARD_END
#endif

View File

@ -10,15 +10,12 @@
CXX_GUARD_START
#include <mgba/core/log.h>
#ifdef USE_DEBUGGERS
#include <mgba/debugger/debugger.h>
#endif
#include <mgba/script/macros.h>
#include <mgba/script/types.h>
mLOG_DECLARE_CATEGORY(SCRIPT);
struct mCore;
struct mScriptTextBuffer;
mSCRIPT_DECLARE_STRUCT(mCore);
@ -81,6 +78,7 @@ struct mScriptContext;
void mScriptContextAttachCore(struct mScriptContext*, struct mCore*);
void mScriptContextDetachCore(struct mScriptContext*);
struct mLogger;
void mScriptContextAttachLogger(struct mScriptContext*, struct mLogger*);
void mScriptContextDetachLogger(struct mScriptContext*);

View File

@ -101,8 +101,6 @@ struct mCoreThreadInternal {
bool mCoreThreadStart(struct mCoreThread* threadContext);
bool mCoreThreadHasStarted(struct mCoreThread* threadContext);
bool mCoreThreadHasExited(struct mCoreThread* threadContext);
bool mCoreThreadHasCrashed(struct mCoreThread* threadContext);
void mCoreThreadMarkCrashed(struct mCoreThread* threadContext);
void mCoreThreadEnd(struct mCoreThread* threadContext);
void mCoreThreadReset(struct mCoreThread* threadContext);
void mCoreThreadJoin(struct mCoreThread* threadContext);
@ -122,6 +120,10 @@ void mCoreThreadPauseFromThread(struct mCoreThread* threadContext);
void mCoreThreadWaitFromThread(struct mCoreThread* threadContext);
void mCoreThreadStopWaiting(struct mCoreThread* threadContext);
bool mCoreThreadHasCrashed(struct mCoreThread* threadContext);
void mCoreThreadMarkCrashed(struct mCoreThread* threadContext);
void mCoreThreadClearCrashed(struct mCoreThread* threadContext);
void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool);
void mCoreThreadRewindParamsChanged(struct mCoreThread* threadContext);

View File

@ -86,6 +86,8 @@ struct GBARTC {
uint8_t control; // Used for status2 on DS
uint8_t alarm2[3];
uint8_t time[7];
time_t lastLatch;
time_t offset;
};
DECL_BITFIELD(GPIOPin, uint16_t);

View File

@ -72,6 +72,7 @@ struct GBASavedata {
uint8_t* data;
enum SavedataCommand command;
struct VFile* vf;
struct GBACartridgeHardware* gpio;
int mapMode;
bool maskWriteback;
@ -93,6 +94,12 @@ struct GBASavedata {
enum FlashStateMachine flashState;
};
struct GBASavedataRTCBuffer {
uint8_t time[7];
uint8_t control;
uint64_t lastLatch;
};
void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf);
void GBASavedataDeinit(struct GBASavedata* savedata);
@ -116,6 +123,9 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount);
void GBASavedataRTCRead(struct GBASavedata* savedata);
void GBASavedataRTCWrite(struct GBASavedata* savedata);
struct GBASerializedState;
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state);
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state);

View File

@ -348,7 +348,8 @@ struct GBASerializedState {
int32_t rtcBits;
int32_t rtcCommandActive;
RTCCommandData rtcCommand;
RTCControl rtcControl;
uint8_t rtcControl;
uint8_t reserved[3];
uint8_t time[7];
uint8_t devices;
uint16_t gyroSample;

View File

@ -0,0 +1,25 @@
/* Copyright (c) 2013-2022 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef M_SCRIPT_SOCKET_H
#define M_SCRIPT_SOCKET_H
enum mSocketErrorCode {
mSCRIPT_SOCKERR_UNKNOWN_ERROR = -1,
mSCRIPT_SOCKERR_OK = 0,
mSCRIPT_SOCKERR_AGAIN,
mSCRIPT_SOCKERR_ADDRESS_IN_USE,
mSCRIPT_SOCKERR_CONNECTION_REFUSED,
mSCRIPT_SOCKERR_DENIED,
mSCRIPT_SOCKERR_FAILED,
mSCRIPT_SOCKERR_NETWORK_UNREACHABLE,
mSCRIPT_SOCKERR_NOT_FOUND,
mSCRIPT_SOCKERR_NO_DATA,
mSCRIPT_SOCKERR_OUT_OF_MEMORY,
mSCRIPT_SOCKERR_TIMEOUT,
mSCRIPT_SOCKERR_UNSUPPORTED,
};
#endif

View File

@ -10,6 +10,7 @@
CXX_GUARD_START
#include <mgba/core/log.h>
#include <mgba/script/types.h>
#include <mgba-util/table.h>
#include <mgba-util/vfs.h>
@ -18,6 +19,8 @@ CXX_GUARD_START
#define mSCRIPT_CONSTANT_PAIR(NS, CONST) { #CONST, mScriptValueCreateFromSInt(NS ## _ ## CONST) }
#define mSCRIPT_KV_SENTINEL { NULL, NULL }
mLOG_DECLARE_CATEGORY(SCRIPT);
struct mScriptFrame;
struct mScriptFunction;
struct mScriptEngineContext;
@ -46,16 +49,21 @@ struct mScriptEngine2 {
struct mScriptEngineContext {
struct mScriptContext* context;
struct mScriptEngine2* engine;
void (*destroy)(struct mScriptEngineContext*);
bool (*isScript)(struct mScriptEngineContext*, const char* name, struct VFile* vf);
bool (*setGlobal)(struct mScriptEngineContext*, const char* name, struct mScriptValue*);
struct mScriptValue* (*getGlobal)(struct mScriptEngineContext*, const char* name);
struct mScriptValue* (*rootScope)(struct mScriptEngineContext*);
bool (*load)(struct mScriptEngineContext*, const char* filename, struct VFile*);
bool (*run)(struct mScriptEngineContext*);
const char* (*getError)(struct mScriptEngineContext*);
struct Table docroot;
};
struct mScriptKVPair {
@ -83,6 +91,7 @@ struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext*, struct
void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref);
void mScriptContextAttachStdlib(struct mScriptContext* context);
void mScriptContextAttachSocket(struct mScriptContext* context);
void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants);
void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value);
@ -93,6 +102,10 @@ void mScriptContextRemoveCallback(struct mScriptContext*, uint32_t cbid);
void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring);
const char* mScriptContextGetDocstring(struct mScriptContext*, const char* key);
void mScriptEngineExportDocNamespace(struct mScriptEngineContext*, const char* nspace, struct mScriptKVPair* value);
void mScriptEngineSetDocstring(struct mScriptEngineContext*, const char* key, const char* docstring);
const char* mScriptEngineGetDocstring(struct mScriptEngineContext*, const char* key);
struct VFile;
bool mScriptContextLoadVF(struct mScriptContext*, const char* name, struct VFile* vf);
bool mScriptContextLoadFile(struct mScriptContext*, const char* path);

View File

@ -182,6 +182,23 @@ CXX_GUARD_START
.init = false, \
.details = (const struct mScriptClassInitDetails[]) {
#define mSCRIPT_DECLARE_DOC_STRUCT(SCOPE, STRUCT) \
static const struct mScriptType mSTStruct_doc_ ## STRUCT;
#define mSCRIPT_DEFINE_DOC_STRUCT(SCOPE, STRUCT) \
static struct mScriptTypeClass _mSTStructDetails_doc_ ## STRUCT; \
static const struct mScriptType mSTStruct_doc_ ## STRUCT = { \
.base = mSCRIPT_TYPE_OBJECT, \
.details = { \
.cls = &_mSTStructDetails_doc_ ## STRUCT \
}, \
.size = 0, \
.name = SCOPE "::struct::" #STRUCT, \
}; \
static struct mScriptTypeClass _mSTStructDetails_doc_ ## STRUCT = { \
.init = false, \
.details = (const struct mScriptClassInitDetails[]) {
#define mSCRIPT_DEFINE_DOCSTRING(DOCSTRING) { \
.type = mSCRIPT_CLASS_INIT_DOCSTRING, \
.info = { \
@ -216,6 +233,10 @@ CXX_GUARD_START
} \
},
#define mSCRIPT_DEFINE_INTERNAL { \
.type = mSCRIPT_CLASS_INIT_INTERNAL \
},
#define _mSCRIPT_STRUCT_METHOD_POP(TYPE, S, NPARAMS, ...) \
_mCALL(_mCAT(mSCRIPT_POP_, _mSUCC_ ## NPARAMS), &frame->arguments, _mCOMMA_ ## NPARAMS(S(TYPE), __VA_ARGS__)); \
if (mScriptListSize(&frame->arguments)) { \
@ -338,10 +359,48 @@ CXX_GUARD_START
#define mSCRIPT_DECLARE_STRUCT_VOID_CD_METHOD_WITH_DEFAULTS(TYPE, NAME, NPARAMS, ...) \
mSCRIPT_DECLARE_STRUCT_VOID_C_METHOD_WITH_DEFAULTS(TYPE, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
#define _mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, NRET, RETURN, NPARAMS, DEFAULTS, ...) \
static const struct mScriptType _mSTStructBindingType_doc_ ## TYPE ## _ ## NAME = { \
.base = mSCRIPT_TYPE_FUNCTION, \
.name = SCOPE "::struct::" #TYPE "." #NAME, \
.details = { \
.function = { \
.parameters = { \
.count = _mSUCC_ ## NPARAMS, \
.entries = { mSCRIPT_TYPE_MS_DS(TYPE), _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \
.names = { "this", _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \
.defaults = DEFAULTS, \
}, \
.returnType = { \
.count = NRET, \
.entries = { RETURN } \
}, \
}, \
} \
};
#define mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, RETURN, NAME, NPARAMS, ...) \
_mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, NULL, __VA_ARGS__)
#define mSCRIPT_DECLARE_DOC_STRUCT_VOID_METHOD(SCOPE, TYPE, NAME, NPARAMS, ...) \
_mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 0, 0, NPARAMS, NULL, __VA_ARGS__)
#define mSCRIPT_DECLARE_DOC_STRUCT_METHOD_WITH_DEFAULTS(SCOPE, TYPE, RETURN, NAME, NPARAMS, ...) \
static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \
_mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, _mIDENT(_mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME), __VA_ARGS__) \
#define mSCRIPT_DECLARE_DOC_STRUCT_VOID_METHOD_WITH_DEFAULTS(SCOPE, TYPE, NAME, NPARAMS, ...) \
static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \
_mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 0, 0, NPARAMS, _mIDENT(_mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME), __VA_ARGS__) \
#define mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(TYPE, NAME) \
static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \
mSCRIPT_NO_DEFAULT,
#define mSCRIPT_DEFINE_DOC_STRUCT_BINDING_DEFAULTS(SCOPE, TYPE, NAME) \
static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \
mSCRIPT_NO_DEFAULT,
#define mSCRIPT_DEFINE_DEFAULTS_END }
#define _mSCRIPT_DEFINE_STRUCT_BINDING(INIT_TYPE, TYPE, EXPORTED_NAME, NAME) { \
@ -366,6 +425,8 @@ CXX_GUARD_START
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(GET, TYPE, _get, _get)
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set)
#define mSCRIPT_DEFINE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME) mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(doc_ ## TYPE, NAME, NAME)
#define mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(TYPE, CAST_TYPE, MEMBER) { \
.type = mSCRIPT_CLASS_INIT_CAST_TO_MEMBER, \
.info = { \
@ -393,8 +454,8 @@ CXX_GUARD_START
.function = { \
.parameters = { \
.count = NPARAMS, \
.entries = { _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \
.names = { _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \
.entries = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \
.names = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \
}, \
.returnType = { \
.count = NRET, \
@ -433,7 +494,40 @@ CXX_GUARD_START
_mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \
return true; \
} \
_mSCRIPT_BIND_FUNCTION(NAME, 0, , NPARAMS, __VA_ARGS__)
_mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NPARAMS, __VA_ARGS__)
#define _mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, NRET, RETURN, NPARAMS, ...) \
static const struct mScriptType _mScriptDocType_ ## NAME = { \
.base = mSCRIPT_TYPE_FUNCTION, \
.name = SCOPE "::function::" #NAME, \
.alloc = NULL, \
.details = { \
.function = { \
.parameters = { \
.count = NPARAMS, \
.entries = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \
.names = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \
}, \
.returnType = { \
.count = NRET, \
.entries = { RETURN } \
}, \
}, \
} \
}; \
const struct mScriptValue _mScriptDoc_ ## NAME = { \
.type = &_mScriptDocType_ ## NAME, \
.refs = mSCRIPT_VALUE_UNREF, \
.value = { \
.copaque = NULL \
} \
}
#define mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, RETURN, NPARAMS, ...) \
_mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DEFINE_DOC_VOID_FUNCTION(SCOPE, NAME, NPARAMS, ...) \
_mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, 0, 0, NPARAMS, __VA_ARGS__)
#define mSCRIPT_MAKE(TYPE, VALUE) (struct mScriptValue) { \
.type = (mSCRIPT_TYPE_MS_ ## TYPE), \

View File

@ -17,6 +17,8 @@ CXX_GUARD_START
#define mSCRIPT_VALUE_UNREF -1
#define mSCRIPT_PARAMS_MAX 8
#define mSCRIPT_VALUE_DOC_FUNCTION(NAME) (&_mScriptDoc_ ## NAME)
#define mSCRIPT_TYPE_C_S8 int8_t
#define mSCRIPT_TYPE_C_U8 uint8_t
#define mSCRIPT_TYPE_C_S16 int16_t
@ -100,6 +102,7 @@ CXX_GUARD_START
#define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper)
#define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE)
#define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE)
#define mSCRIPT_TYPE_MS_DS(STRUCT) (&mSTStruct_doc_ ## STRUCT)
#define mSCRIPT_TYPE_CMP_GENERIC(TYPE0, TYPE1) (TYPE0 == TYPE1)
#define mSCRIPT_TYPE_CMP_U8(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_U8, TYPE)
@ -151,6 +154,7 @@ enum mScriptClassInitType {
mSCRIPT_CLASS_INIT_DEINIT,
mSCRIPT_CLASS_INIT_GET,
mSCRIPT_CLASS_INIT_SET,
mSCRIPT_CLASS_INIT_INTERNAL,
};
enum {
@ -242,6 +246,7 @@ struct mScriptTypeClass {
const struct mScriptClassInitDetails* details;
const struct mScriptType* parent;
const char* docstring;
bool internal;
struct Table instanceMembers;
struct Table castToMembers;
struct mScriptClassMember* alloc; // TODO

View File

@ -1,13 +1,16 @@
Akatsuki
AVjoyu__Chan
Benedikt Feih
Brandon
Emily A. Bellows
G
gocha
Jaime J. Denizard
MichaelK__
Miras Absar
Petru-Sebastian Toader
SquidHominid
Stevoisiak
Tyler Jenkins
William K. Leung
Zach
Zhongchao Qian

View File

@ -0,0 +1,103 @@
lastkeys = nil
server = nil
ST_sockets = {}
nextID = 1
local KEY_NAMES = { "A", "B", "s", "S", "<", ">", "^", "v", "R", "L" }
function ST_stop(id)
local sock = ST_sockets[id]
ST_sockets[id] = nil
sock:close()
end
function ST_format(id, msg, isError)
local prefix = "Socket " .. id
if isError then
prefix = prefix .. " Error: "
else
prefix = prefix .. " Received: "
end
return prefix .. msg
end
function ST_error(id, err)
console:error(ST_format(id, err, true))
ST_stop(id)
end
function ST_received(id)
local sock = ST_sockets[id]
if not sock then return end
while true do
local p, err = sock:receive(1024)
if p then
console:log(ST_format(id, p:match("^(.-)%s*$")))
else
if err ~= socket.ERRORS.AGAIN then
console:error(ST_format(id, err, true))
ST_stop(id)
end
return
end
end
end
function ST_scankeys()
local keys = emu:getKeys()
if keys ~= lastkeys then
lastkeys = keys
local msg = "["
for i, k in ipairs(KEY_NAMES) do
if (keys & (1 << (i - 1))) == 0 then
msg = msg .. " "
else
msg = msg .. k;
end
end
msg = msg .. "]\n"
for id, sock in pairs(ST_sockets) do
if sock then sock:send(msg) end
end
end
end
function ST_accept()
local sock, err = server:accept()
if err then
console:error(ST_format("Accept", err, true))
return
end
local id = nextID
nextID = id + 1
ST_sockets[id] = sock
sock:add("received", function() ST_received(id) end)
sock:add("error", function() ST_error(id) end)
console:log(ST_format(id, "Connected"))
end
callbacks:add("keysRead", ST_scankeys)
local port = 8888
server = nil
while not server do
server, err = socket.bind(nil, port)
if err then
if err == socket.ERRORS.ADDRESS_IN_USE then
port = port + 1
else
console:error(ST_format("Bind", err, true))
break
end
else
local ok
ok, err = server:listen()
if err then
server:close()
console:error(ST_format("Listen", err, true))
else
console:log("Socket Server Test: Listening on port " .. port)
server:add("received", ST_accept)
end
end
end

View File

@ -0,0 +1,69 @@
sockettest = nil
lastkeys = nil
local KEY_NAMES = { "A", "B", "s", "S", "<", ">", "^", "v", "R", "L" }
function ST_stop()
if not sockettest then return end
console:log("Socket Test: Shutting down")
sockettest:close()
sockettest = nil
end
function ST_start()
ST_stop()
console:log("Socket Test: Connecting to 127.0.0.1:8888...")
sockettest = socket.tcp()
sockettest:add("received", ST_received)
sockettest:add("error", ST_error)
if sockettest:connect("127.0.0.1", 8888) then
console:log("Socket Test: Connected")
lastkeys = nil
else
console:log("Socket Test: Failed to connect")
ST_stop()
end
end
function ST_error(err)
console:error("Socket Test Error: " .. err)
ST_stop()
end
function ST_received()
while true do
local p, err = sockettest:receive(1024)
if p then
console:log("Socket Test Received: " .. p:match("^(.-)%s*$"))
else
if err ~= socket.ERRORS.AGAIN then
console:error("Socket Test Error: " .. err)
ST_stop()
end
return
end
end
end
function ST_scankeys()
if not sockettest then return end
local keys = emu:getKeys()
if keys ~= lastkeys then
lastkeys = keys
local msg = "["
for i, k in ipairs(KEY_NAMES) do
if (keys & (1 << (i - 1))) == 0 then
msg = msg .. " "
else
msg = msg .. k;
end
end
sockettest:send(msg .. "]\n")
end
end
callbacks:add("start", ST_start)
callbacks:add("stop", ST_stop)
callbacks:add("crashed", ST_stop)
callbacks:add("reset", ST_start)
callbacks:add("keysRead", ST_scankeys)

View File

@ -24,7 +24,7 @@ set(TEST_FILES
test/core.c)
if(ENABLE_SCRIPTING)
list(APPEND SOURCE_FILES
set(SCRIPTING_FILES
scripting.c)
if(USE_LUA)
@ -34,7 +34,9 @@ if(ENABLE_SCRIPTING)
endif()
source_group("mCore" FILES ${SOURCE_FILES})
source_group("mCore scripting" FILES ${SCRIPTING_FILES})
source_group("mCore tests" FILES ${TEST_FILES})
export_directory(CORE SOURCE_FILES)
export_directory(CORE_SCRIPT SCRIPTING_FILES)
export_directory(CORE_TEST TEST_FILES)

View File

@ -526,6 +526,15 @@ void mCoreThreadMarkCrashed(struct mCoreThread* threadContext) {
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadClearCrashed(struct mCoreThread* threadContext) {
MutexLock(&threadContext->impl->stateMutex);
if (threadContext->impl->state == mTHREAD_CRASHED) {
threadContext->impl->state = mTHREAD_REQUEST;
ConditionWake(&threadContext->impl->stateCond);
}
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadEnd(struct mCoreThread* threadContext) {
MutexLock(&threadContext->impl->stateMutex);
_waitOnInterrupt(threadContext->impl);
@ -685,6 +694,10 @@ void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) {
MutexLock(&threadContext->impl->stateMutex);
threadContext->impl->rewinding = rewinding;
if (rewinding && threadContext->impl->state == mTHREAD_CRASHED) {
threadContext->impl->state = mTHREAD_REQUEST;
ConditionWake(&threadContext->impl->stateCond);
}
MutexUnlock(&threadContext->impl->stateMutex);
}

View File

@ -326,16 +326,24 @@ static void _printHelp(struct CLIDebugger* debugger, struct CLIDebugVector* dv)
debugger->backend->printf(debugger->backend, "Generic commands:\n");
_printCommands(debugger, _debuggerCommands, _debuggerCommandAliases);
if (debugger->system) {
debugger->backend->printf(debugger->backend, "\n%s commands:\n", debugger->system->platformName);
_printCommands(debugger, debugger->system->platformCommands, debugger->system->platformCommandAliases);
debugger->backend->printf(debugger->backend, "\n%s commands:\n", debugger->system->name);
_printCommands(debugger, debugger->system->commands, debugger->system->commandAliases);
if (debugger->system->platformCommands) {
debugger->backend->printf(debugger->backend, "\n%s commands:\n", debugger->system->platformName);
_printCommands(debugger, debugger->system->platformCommands, debugger->system->platformCommandAliases);
}
if (debugger->system->commands) {
debugger->backend->printf(debugger->backend, "\n%s commands:\n", debugger->system->name);
_printCommands(debugger, debugger->system->commands, debugger->system->commandAliases);
}
}
} else {
_printCommandSummary(debugger, dv->charValue, _debuggerCommands, _debuggerCommandAliases);
if (debugger->system) {
_printCommandSummary(debugger, dv->charValue, debugger->system->platformCommands, debugger->system->platformCommandAliases);
_printCommandSummary(debugger, dv->charValue, debugger->system->commands, debugger->system->commandAliases);
if (debugger->system->platformCommands) {
_printCommandSummary(debugger, dv->charValue, debugger->system->platformCommands, debugger->system->platformCommandAliases);
}
if (debugger->system->commands) {
_printCommandSummary(debugger, dv->charValue, debugger->system->commands, debugger->system->commandAliases);
}
}
}
}
@ -981,8 +989,10 @@ bool CLIDebuggerRunCommand(struct CLIDebugger* debugger, const char* line, size_
}
int result = _tryCommands(debugger, _debuggerCommands, _debuggerCommandAliases, line, cmdLength, args, count - cmdLength - 1);
if (result < 0 && debugger->system) {
result = _tryCommands(debugger, debugger->system->commands, debugger->system->commandAliases, line, cmdLength, args, count - cmdLength - 1);
if (result < 0) {
if (debugger->system->commands) {
result = _tryCommands(debugger, debugger->system->commands, debugger->system->commandAliases, line, cmdLength, args, count - cmdLength - 1);
}
if (result < 0 && debugger->system->platformCommands) {
result = _tryCommands(debugger, debugger->system->platformCommands, debugger->system->platformCommandAliases, line, cmdLength, args, count - cmdLength - 1);
}
}

View File

@ -377,11 +377,13 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
}
void GBAudioWriteNR50(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
audio->volumeRight = GBRegisterNR50GetVolumeRight(value);
audio->volumeLeft = GBRegisterNR50GetVolumeLeft(value);
}
void GBAudioWriteNR51(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
audio->ch1Right = GBRegisterNR51GetCh1Right(value);
audio->ch2Right = GBRegisterNR51GetCh2Right(value);
audio->ch3Right = GBRegisterNR51GetCh3Right(value);

View File

@ -33,6 +33,7 @@ static const uint8_t _registeredTrademark[] = {0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA
#define SGB_BIOS_CHECKSUM 0xEC8A83B9
#define SGB2_BIOS_CHECKSUM 0X53D0DD63
#define CGB_BIOS_CHECKSUM 0x41884E46
#define CGB0_BIOS_CHECKSUM 0xE8EF5318
#define AGB_BIOS_CHECKSUM 0xFFD6B0F1
mLOG_DEFINE_CATEGORY(GB, "GB", "gb");
@ -275,13 +276,17 @@ void GBResizeSram(struct GB* gb, size_t size) {
vf->seek(vf, size, SEEK_SET);
vf->write(vf, extdataBuffer, vfSize & 0xFF);
}
gb->memory.sram = vf->map(vf, size, MAP_WRITE);
memset(&gb->memory.sram[vfSize], 0xFF, size - vfSize);
if (size) {
gb->memory.sram = vf->map(vf, size, MAP_WRITE);
memset(&gb->memory.sram[vfSize], 0xFF, size - vfSize);
}
} else if (size > gb->sramSize || !gb->memory.sram) {
if (gb->memory.sram) {
vf->unmap(vf, gb->memory.sram, gb->sramSize);
}
gb->memory.sram = vf->map(vf, size, MAP_WRITE);
if (size) {
gb->memory.sram = vf->map(vf, size, MAP_WRITE);
}
}
} else {
if (gb->memory.sram) {
@ -295,9 +300,11 @@ void GBResizeSram(struct GB* gb, size_t size) {
gb->sramVf = newVf;
vf->truncate(vf, size);
}
gb->memory.sram = vf->map(vf, size, MAP_READ);
if (size) {
gb->memory.sram = vf->map(vf, size, MAP_READ);
}
}
if (gb->memory.sram == (void*) -1) {
if (!size || gb->memory.sram == (void*) -1) {
gb->memory.sram = NULL;
}
} else if (size) {
@ -513,6 +520,7 @@ bool GBIsBIOS(struct VFile* vf) {
case SGB_BIOS_CHECKSUM:
case SGB2_BIOS_CHECKSUM:
case CGB_BIOS_CHECKSUM:
case CGB0_BIOS_CHECKSUM:
case AGB_BIOS_CHECKSUM:
return true;
default:
@ -844,39 +852,60 @@ void GBUpdateIRQs(struct GB* gb) {
SM83RaiseIRQ(gb->cpu);
}
static void _GBAdvanceCycles(struct GB* gb) {
struct SM83Core* cpu = gb->cpu;
int stateMask = (4 * (2 - gb->doubleSpeed)) - 1;
int stateOffset = ((cpu->nextEvent - cpu->cycles) & stateMask) >> !gb->doubleSpeed;
cpu->cycles = cpu->nextEvent;
cpu->executionState = (cpu->executionState + stateOffset) & 3;
}
void GBProcessEvents(struct SM83Core* cpu) {
struct GB* gb = (struct GB*) cpu->master;
do {
int32_t cycles = cpu->cycles;
int32_t nextEvent;
cpu->cycles = 0;
cpu->nextEvent = INT_MAX;
nextEvent = cycles;
bool wasHalted = cpu->halted;
while (true) {
do {
#ifdef USE_DEBUGGERS
gb->timing.globalCycles += nextEvent;
#endif
nextEvent = mTimingTick(&gb->timing, nextEvent);
} while (gb->cpuBlocked);
// This loop cannot early exit until the SM83 run loop properly handles mid-M-cycle-exits
cpu->nextEvent = nextEvent;
int32_t cycles = cpu->cycles;
int32_t nextEvent;
if (cpu->halted) {
cpu->cycles = cpu->nextEvent;
if (!gb->memory.ie || !gb->memory.ime) {
cpu->cycles = 0;
cpu->nextEvent = INT_MAX;
nextEvent = cycles;
do {
#ifdef USE_DEBUGGERS
gb->timing.globalCycles += nextEvent;
#endif
nextEvent = mTimingTick(&gb->timing, nextEvent);
} while (gb->cpuBlocked);
// This loop cannot early exit until the SM83 run loop properly handles mid-M-cycle-exits
cpu->nextEvent = nextEvent;
if (cpu->halted) {
_GBAdvanceCycles(gb);
if (!gb->memory.ie || !gb->memory.ime) {
break;
}
}
if (gb->earlyExit) {
break;
}
} while (cpu->cycles >= cpu->nextEvent);
if (gb->cpuBlocked) {
_GBAdvanceCycles(gb);
}
if (gb->earlyExit) {
if (!wasHalted || (cpu->executionState & 3) == SM83_CORE_FETCH) {
break;
}
} while (cpu->cycles >= cpu->nextEvent);
gb->earlyExit = false;
if (gb->cpuBlocked) {
cpu->cycles = cpu->nextEvent;
int nextFetch = (SM83_CORE_FETCH - cpu->executionState) * cpu->tMultiplier;
if (nextFetch < cpu->nextEvent) {
cpu->cycles += nextFetch;
cpu->executionState = SM83_CORE_FETCH;
break;
}
_GBAdvanceCycles(gb);
}
gb->earlyExit = false;
}
void GBSetInterrupts(struct SM83Core* cpu, bool enable) {
@ -928,7 +957,8 @@ static void _enableInterrupts(struct mTiming* timing, void* user, uint32_t cycle
void GBHalt(struct SM83Core* cpu) {
struct GB* gb = (struct GB*) cpu->master;
if (!(gb->memory.ie & gb->memory.io[GB_REG_IF] & 0x1F)) {
cpu->cycles = cpu->nextEvent;
_GBAdvanceCycles(gb);
cpu->executionState = (cpu->executionState - 1) & 3;
cpu->halted = true;
} else if (!gb->memory.ime) {
mLOG(GB, GAME_ERROR, "HALT bug");

View File

@ -100,6 +100,9 @@ void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) {
hw->rtc.freeReg = 0;
hw->rtc.status1 = 0;
memset(hw->rtc.time, 0, sizeof(hw->rtc.time));
hw->rtc.lastLatch = 0;
hw->rtc.offset = 0;
}
void _readPins(struct GBACartridgeHardware* hw) {
@ -270,6 +273,7 @@ unsigned GBARTCOutput(struct GBARTC* rtc) {
break;
case RTC_FREE_REG:
outputByte = rtc->freeReg;
break;
case RTC_FORCE_IRQ:
case RTC_ALARM1:
case RTC_ALARM2:
@ -293,6 +297,9 @@ void _rtcUpdateClock(struct GBARTC* rtc, struct mRTCSource* source) {
} else {
t = time(0);
}
rtc->lastLatch = t;
t -= rtc->offset;
struct tm date;
localtime_r(&t, &date);
rtc->time[0] = _rtcBCD(date.tm_year - 100);

View File

@ -77,6 +77,7 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
gba->memory.savedata.timing = &gba->timing;
gba->memory.savedata.vf = NULL;
gba->memory.savedata.realVf = NULL;
gba->memory.savedata.gpio = &gba->memory.hw;
GBASavedataInit(&gba->memory.savedata, NULL);
gba->video.p = gba;
@ -364,7 +365,6 @@ bool GBALoadNull(struct GBA* gba) {
gba->yankedRomSize = 0;
gba->memory.romSize = SIZE_CART0;
gba->memory.romMask = SIZE_CART0 - 1;
gba->memory.mirroring = false;
gba->romCrc32 = 0;
if (gba->cpu) {
@ -419,6 +419,19 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) {
gba->memory.romSize = SIZE_CART0;
}
gba->pristineRomSize = SIZE_CART0;
} else if (gba->pristineRomSize == 0x00100000) {
// 1 MiB ROMs (e.g. Classic NES) all appear as 4x mirrored, but not more
gba->isPristine = false;
gba->memory.romSize = 0x00400000;
#ifdef FIXED_ROM_BUFFER
gba->memory.rom = romBuffer;
#else
gba->memory.rom = anonymousMemoryMap(SIZE_CART0);
#endif
vf->read(vf, gba->memory.rom, gba->pristineRomSize);
memcpy(&gba->memory.rom[0x40000], gba->memory.rom, 0x00100000);
memcpy(&gba->memory.rom[0x80000], gba->memory.rom, 0x00100000);
memcpy(&gba->memory.rom[0xC0000], gba->memory.rom, 0x00100000);
} else {
gba->memory.rom = vf->map(vf, gba->pristineRomSize, MAP_READ);
gba->memory.romSize = gba->pristineRomSize;
@ -430,8 +443,7 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) {
}
gba->yankedRomSize = 0;
gba->memory.romMask = toPow2(gba->memory.romSize) - 1;
gba->memory.mirroring = false;
gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize);
gba->romCrc32 = doCrc32(gba->memory.rom, gba->pristineRomSize);
if (popcount32(gba->memory.romSize) != 1) {
// This ROM is either a bad dump or homebrew. Emulate flash cart behavior.
#ifndef FIXED_ROM_BUFFER

View File

@ -83,7 +83,6 @@ void GBAMemoryInit(struct GBA* gba) {
cpu->memory.activeNonseqCycles32 = 0;
cpu->memory.activeNonseqCycles16 = 0;
gba->memory.biosPrefetch = 0;
gba->memory.mirroring = false;
gba->memory.agbPrintProtect = 0;
memset(&gba->memory.agbPrintCtx, 0, sizeof(gba->memory.agbPrintCtx));
@ -277,9 +276,6 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
if (newRegion < REGION_CART0 || (address & (SIZE_CART0 - 1)) < memory->romSize) {
return;
}
if (memory->mirroring && (address & memory->romMask) < memory->romSize) {
return;
}
}
if (memory->activeRegion == REGION_BIOS) {
@ -411,8 +407,6 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
wait += waitstatesRegion[address >> BASE_OFFSET]; \
if ((address & (SIZE_CART0 - 1)) < memory->romSize) { \
LOAD_32(value, address & (SIZE_CART0 - 4), memory->rom); \
} else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { \
LOAD_32(value, address & memory->romMask & -4, memory->rom); \
} else if (memory->vfame.cartType) { \
value = GBAVFameGetPatternValue(address, 32); \
} else { \
@ -578,8 +572,6 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom);
} else if (memory->mirroring && (address & memory->romMask) < memory->romSize) {
LOAD_16(value, address & memory->romMask, memory->rom);
} else if (memory->vfame.cartType) {
value = GBAVFameGetPatternValue(address, 16);
} else if ((address & (SIZE_CART0 - 1)) >= AGB_PRINT_BASE) {
@ -605,8 +597,6 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
value = GBACartEReaderRead(&memory->ereader, address);
} else if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom);
} else if (memory->mirroring && (address & memory->romMask) < memory->romSize) {
LOAD_16(value, address & memory->romMask, memory->rom);
} else if (memory->vfame.cartType) {
value = GBAVFameGetPatternValue(address, 16);
} else {
@ -698,8 +688,6 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
value = ((uint8_t*) memory->rom)[address & (SIZE_CART0 - 1)];
} else if (memory->mirroring && (address & memory->romMask) < memory->romSize) {
value = ((uint8_t*) memory->rom)[address & memory->romMask];
} else if (memory->vfame.cartType) {
value = GBAVFameGetPatternValue(address, 8);
} else {

View File

@ -229,7 +229,6 @@ bool GBAOverrideFind(const struct Configuration* config, struct GBACartridgeOver
if (!found && override->id[0] == 'F') {
// Classic NES Series
override->savetype = SAVEDATA_EEPROM;
override->mirroring = true;
found = true;
}
@ -342,6 +341,7 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri
if (override->hardware & HW_RTC) {
GBAHardwareInitRTC(&gba->memory.hw);
GBASavedataRTCRead(&gba->memory.savedata);
}
if (override->hardware & HW_GYRO) {
@ -377,10 +377,6 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri
gba->idleOptimization = IDLE_LOOP_REMOVE;
}
}
if (override->mirroring) {
gba->memory.mirroring = true;
}
}
void GBAOverrideApplyDefaults(struct GBA* gba, const struct Configuration* overrides) {

View File

@ -575,6 +575,7 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
if (savedata->mapMode & MAP_WRITE) {
size_t size = GBASavedataSize(savedata);
if (savedata->data && savedata->vf->sync(savedata->vf, savedata->data, size)) {
GBASavedataRTCWrite(savedata);
mLOG(GBA_SAVE, INFO, "Savedata synced");
} else {
mLOG(GBA_SAVE, INFO, "Savedata failed to sync!");
@ -583,6 +584,58 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
}
}
void GBASavedataRTCWrite(struct GBASavedata* savedata) {
if (!(savedata->gpio->devices & HW_RTC)) {
return;
}
struct GBASavedataRTCBuffer buffer;
memcpy(&buffer.time, savedata->gpio->rtc.time, 7);
buffer.control = savedata->gpio->rtc.control;
STORE_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch);
size_t size = GBASavedataSize(savedata) & ~0xFF;
savedata->vf->seek(savedata->vf, size, SEEK_SET);
savedata->vf->write(savedata->vf, &buffer, sizeof(buffer));
}
static uint8_t _unBCD(uint8_t byte) {
return (byte >> 4) * 10 + (byte & 0xF);
}
void GBASavedataRTCRead(struct GBASavedata* savedata) {
struct GBASavedataRTCBuffer buffer;
size_t size = GBASavedataSize(savedata) & ~0xFF;
savedata->vf->seek(savedata->vf, size, SEEK_SET);
size = savedata->vf->read(savedata->vf, &buffer, sizeof(buffer));
if (size < sizeof(buffer)) {
return;
}
memcpy(savedata->gpio->rtc.time, &buffer.time, 7);
// Older FlashGBX sets this to 0x01 instead of the control flag.
// Since that bit is invalid on hardware, we can check for != 0x01
// to see if it's a valid value instead of just a filler value.
if (buffer.control != 1) {
savedata->gpio->rtc.control = buffer.control;
}
LOAD_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch);
struct tm date;
date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100;
date.tm_mon = _unBCD(savedata->gpio->rtc.time[1]) - 1;
date.tm_mday = _unBCD(savedata->gpio->rtc.time[2]);
date.tm_hour = _unBCD(savedata->gpio->rtc.time[4]);
date.tm_min = _unBCD(savedata->gpio->rtc.time[5]);
date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]);
date.tm_isdst = -1;
savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - mktime(&date);
}
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {
state->savedata.type = savedata->type;
state->savedata.command = savedata->command;

View File

@ -137,6 +137,9 @@ ssize_t _vf3dWrite(struct VFile* vf, const void* buffer, size_t size) {
static void* _vf3dMap(struct VFile* vf, size_t size, int flags) {
struct VFile3DS* vf3d = (struct VFile3DS*) vf;
UNUSED(flags);
if (!size) {
return NULL;
}
void* buffer = anonymousMemoryMap(size);
if (buffer) {
u32 sizeRead;

View File

@ -1360,8 +1360,8 @@ static void _updateRotation(struct mRotationSource* source) {
gyroZ = 0;
_initSensors();
if (tiltEnabled) {
tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * 3e8f;
tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * -3e8f;
tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * -2e8f;
tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * 2e8f;
}
if (gyroEnabled) {
gyroZ = sensorGetCallback(0, RETRO_SENSOR_GYROSCOPE_Z) * -1.1e9f;

View File

@ -113,6 +113,9 @@ ssize_t _vfsceWrite(struct VFile* vf, const void* buffer, size_t size) {
static void* _vfsceMap(struct VFile* vf, size_t size, int flags) {
struct VFileSce* vfsce = (struct VFileSce*) vf;
UNUSED(flags);
if (!size) {
return NULL;
}
void* buffer = anonymousMemoryMap(size);
if (buffer) {
SceOff cur = sceIoLseek(vfsce->fd, 0, SEEK_CUR);

View File

@ -618,6 +618,7 @@ void CoreController::loadState(int slot) {
m_stateSlot = slot;
m_backupSaveState.clear();
}
mCoreThreadClearCrashed(&m_threadContext);
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
CoreController* controller = static_cast<CoreController*>(context->userData);
if (!controller->m_backupLoadState.isOpen()) {
@ -637,6 +638,7 @@ void CoreController::loadState(const QString& path, int flags) {
if (flags != -1) {
m_loadStateFlags = flags;
}
mCoreThreadClearCrashed(&m_threadContext);
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
CoreController* controller = static_cast<CoreController*>(context->userData);
VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
@ -665,6 +667,7 @@ void CoreController::loadState(QIODevice* iodev, int flags) {
if (flags != -1) {
m_loadStateFlags = flags;
}
mCoreThreadClearCrashed(&m_threadContext);
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
CoreController* controller = static_cast<CoreController*>(context->userData);
VFile* vf = controller->m_stateVf;

View File

@ -301,7 +301,9 @@ void DisplayGL::pauseDrawing() {
if (m_hasStarted) {
m_isDrawing = false;
QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
setUpdatesEnabled(true);
if (QGuiApplication::platformName() != "xcb") {
setUpdatesEnabled(true);
}
}
}

View File

@ -12,6 +12,7 @@
#include <QSysInfo>
#include <QWindow>
#include <mgba/core/cheats.h>
#include <mgba/core/serialize.h>
#include <mgba/core/version.h>
#include <mgba-util/png-io.h>
@ -266,6 +267,17 @@ void ReportView::generateReport() {
}
addROMInfo(windowReport, controller.get());
mCheatDevice* device = controller->cheatDevice();
if (device) {
VFileDevice vf(VFileDevice::openMemory());
mCheatSaveFile(device, vf);
vf.seek(0);
QByteArray cheats(vf.readAll());
if (cheats.size()) {
addReport(QString("Cheats %1").arg(winId), QString::fromUtf8(cheats));
}
}
if (m_ui.includeSave->isChecked() && !m_ui.includeState->isChecked()) {
// Only do the save separately if savestates aren't enabled, to guarantee consistency
mCore* core = controller->thread()->core;

View File

@ -23,6 +23,9 @@ bool VideoDumper::present(const QVideoFrame& frame) {
QVideoFrame::PixelFormat vFormat = mappedFrame.pixelFormat();
QImage::Format format = QVideoFrame::imageFormatFromPixelFormat(vFormat);
bool swap = false;
#ifdef USE_FFMPEG
bool useScaler = false;
#endif
if (format == QImage::Format_Invalid) {
if (vFormat < QVideoFrame::Format_BGRA5658_Premultiplied) {
vFormat = static_cast<QVideoFrame::PixelFormat>(vFormat - QVideoFrame::Format_BGRA32 + QVideoFrame::Format_ARGB32);
@ -34,11 +37,70 @@ bool VideoDumper::present(const QVideoFrame& frame) {
}
swap = true;
} else {
#ifdef USE_FFMPEG
enum AVPixelFormat pixelFormat;
switch (vFormat) {
case QVideoFrame::Format_YUV420P:
pixelFormat = AV_PIX_FMT_YUV420P;
break;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
case QVideoFrame::Format_YUV422P:
pixelFormat = AV_PIX_FMT_YUV422P;
break;
#endif
case QVideoFrame::Format_YUYV:
pixelFormat = AV_PIX_FMT_YUYV422;
break;
case QVideoFrame::Format_UYVY:
pixelFormat = AV_PIX_FMT_UYVY422;
break;
case QVideoFrame::Format_NV12:
pixelFormat = AV_PIX_FMT_NV12;
break;
case QVideoFrame::Format_NV21:
pixelFormat = AV_PIX_FMT_NV12;
break;
default:
return false;
}
format = QImage::Format_RGB888;
useScaler = true;
if (pixelFormat != m_pixfmt || m_scalerSize != mappedFrame.size()) {
if (m_scaler) {
sws_freeContext(m_scaler);
}
m_scaler = sws_getContext(mappedFrame.width(), mappedFrame.height(), pixelFormat,
mappedFrame.width(), mappedFrame.height(), AV_PIX_FMT_RGB24,
SWS_POINT, nullptr, nullptr, nullptr);
m_scalerSize = mappedFrame.size();
m_pixfmt = pixelFormat;
}
#else
return false;
#endif
}
}
uchar* bits = mappedFrame.bits();
#ifdef USE_FFMPEG
QImage image;
if (!useScaler) {
image = QImage(bits, mappedFrame.width(), mappedFrame.height(), mappedFrame.bytesPerLine(), format);
}
if (useScaler) {
image = QImage(mappedFrame.width(), mappedFrame.height(), format);
const uint8_t* planes[8] = {0};
int strides[8] = {0};
for (int plane = 0; plane < mappedFrame.planeCount(); ++plane) {
planes[plane] = mappedFrame.bits(plane);
strides[plane] = mappedFrame.bytesPerLine(plane);
}
uint8_t* outBits = image.bits();
int outStride = image.bytesPerLine();
sws_scale(m_scaler, planes, strides, 0, mappedFrame.height(), &outBits, &outStride);
} else
#else
QImage image(bits, mappedFrame.width(), mappedFrame.height(), mappedFrame.bytesPerLine(), format);
#endif
if (swap) {
image = image.rgbSwapped();
} else if (surfaceFormat().scanLineDirection() != QVideoSurfaceFormat::BottomToTop) {
@ -66,5 +128,15 @@ QList<QVideoFrame::PixelFormat> VideoDumper::supportedPixelFormats(QAbstractVide
list.append(QVideoFrame::Format_BGRA32_Premultiplied);
list.append(QVideoFrame::Format_BGR565);
list.append(QVideoFrame::Format_BGR555);
#ifdef USE_FFMPEG
list.append(QVideoFrame::Format_YUYV);
list.append(QVideoFrame::Format_UYVY);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
list.append(QVideoFrame::Format_YUV422P);
#endif
list.append(QVideoFrame::Format_YUV420P);
list.append(QVideoFrame::Format_NV12);
list.append(QVideoFrame::Format_NV21);
#endif
return list;
}

View File

@ -7,6 +7,12 @@
#include <QAbstractVideoSurface>
#ifdef USE_FFMPEG
extern "C" {
#include <libswscale/swscale.h>
}
#endif
namespace QGBA {
class VideoDumper : public QAbstractVideoSurface {
@ -20,6 +26,13 @@ public:
signals:
void imageAvailable(const QImage& image);
private:
#ifdef USE_FFMPEG
AVPixelFormat m_pixfmt = AV_PIX_FMT_NONE;
SwsContext* m_scaler = nullptr;
QSize m_scalerSize;
#endif
};
}

View File

@ -50,7 +50,9 @@
#include "ReportView.h"
#include "ROMInfo.h"
#include "SaveConverter.h"
#ifdef ENABLE_SCRIPTING
#include "scripting/ScriptingView.h"
#endif
#include "SensorView.h"
#include "ShaderSelector.h"
#include "ShortcutController.h"
@ -1906,7 +1908,7 @@ void Window::setupOptions() {
ConfigOption* showOSD = m_config->addOption("showOSD");
showOSD->connect([this](const QVariant& value) {
if (m_display) {
if (m_display && !value.isNull()) {
m_display->showOSDMessages(value.toBool());
}
}, this);

View File

@ -113,6 +113,7 @@ void ScriptingController::runCode(const QString& code) {
void ScriptingController::init() {
mScriptContextInit(&m_scriptContext);
mScriptContextAttachStdlib(&m_scriptContext);
mScriptContextAttachSocket(&m_scriptContext);
mScriptContextRegisterEngines(&m_scriptContext);
mScriptContextAttachLogger(&m_scriptContext, &m_logger);

View File

@ -1,6 +1,7 @@
include(ExportDirectory)
set(SOURCE_FILES
context.c
socket.c
stdlib.c
types.c)

View File

@ -8,6 +8,8 @@
#include <mgba/internal/script/lua.h>
#endif
#define KEY_NAME_MAX 128
struct mScriptFileInfo {
const char* name;
struct VFile* vf;
@ -302,6 +304,29 @@ const char* mScriptContextGetDocstring(struct mScriptContext* context, const cha
return HashTableLookup(&context->docstrings, key);
}
void mScriptEngineExportDocNamespace(struct mScriptEngineContext* ctx, const char* nspace, struct mScriptKVPair* values) {
struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
size_t i;
for (i = 0; values[i].key; ++i) {
struct mScriptValue* key = mScriptStringCreateFromUTF8(values[i].key);
mScriptTableInsert(table, key, values[i].value);
mScriptValueDeref(key);
}
HashTableInsert(&ctx->docroot, nspace, table);
}
void mScriptEngineSetDocstring(struct mScriptEngineContext* ctx, const char* key, const char* docstring) {
char scopedKey[KEY_NAME_MAX];
snprintf(scopedKey, sizeof(scopedKey), "%s::%s", ctx->engine->name, key);
HashTableInsert(&ctx->context->docstrings, scopedKey, (char*) docstring);
}
const char* mScriptEngineGetDocstring(struct mScriptEngineContext* ctx, const char* key) {
char scopedKey[KEY_NAME_MAX];
snprintf(scopedKey, sizeof(scopedKey), "%s::%s", ctx->engine->name, key);
return HashTableLookup(&ctx->context->docstrings, scopedKey);
}
bool mScriptContextLoadVF(struct mScriptContext* context, const char* name, struct VFile* vf) {
struct mScriptFileInfo info = {
.name = name,

View File

@ -7,12 +7,33 @@
#include <mgba/core/scripting.h>
#include <mgba/core/version.h>
#include <mgba/script/context.h>
#include <mgba-util/string.h>
struct mScriptContext context;
struct Table types;
FILE* out;
const struct mScriptType* const baseTypes[] = {
mSCRIPT_TYPE_MS_S8,
mSCRIPT_TYPE_MS_U8,
mSCRIPT_TYPE_MS_S16,
mSCRIPT_TYPE_MS_U16,
mSCRIPT_TYPE_MS_S32,
mSCRIPT_TYPE_MS_U32,
mSCRIPT_TYPE_MS_F32,
mSCRIPT_TYPE_MS_S64,
mSCRIPT_TYPE_MS_U64,
mSCRIPT_TYPE_MS_F64,
mSCRIPT_TYPE_MS_STR,
mSCRIPT_TYPE_MS_CHARP,
mSCRIPT_TYPE_MS_LIST,
mSCRIPT_TYPE_MS_TABLE,
mSCRIPT_TYPE_MS_WRAPPER,
NULL
};
void explainValue(struct mScriptValue* value, const char* name, int level);
void explainValueScoped(struct mScriptValue* value, const char* name, const char* scope, int level);
void explainType(struct mScriptType* type, int level);
void addTypesFromTuple(const struct mScriptTypeTuple*);
@ -154,7 +175,7 @@ bool printval(const struct mScriptValue* value, char* buffer, size_t bufferSize)
return false;
}
void explainTable(struct mScriptValue* value, const char* name, int level) {
void explainTable(struct mScriptValue* value, const char* name, const char* scope, int level) {
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
@ -171,9 +192,9 @@ void explainTable(struct mScriptValue* value, const char* name, int level) {
struct mScriptValue string;
if (mScriptCast(mSCRIPT_TYPE_MS_CHARP, k, &string)) {
snprintf(keyval, sizeof(keyval), "%s.%s", name, (const char*) string.value.opaque);
explainValue(v, keyval, level + 1);
explainValueScoped(v, keyval, scope, level + 1);
} else {
explainValue(v, NULL, level + 1);
explainValueScoped(v, NULL, scope, level + 1);
}
} while (mScriptTableIteratorNext(value, &iter));
}
@ -187,6 +208,9 @@ void explainClass(struct mScriptTypeClass* cls, int level) {
if (cls->parent) {
fprintf(out, "%sparent: %s\n", indent, cls->parent->name);
}
if (cls->internal) {
fprintf(out, "%sinternal: true\n", indent);
}
if (cls->docstring) {
if (strchr(cls->docstring, '\n')) {
fprintf(out, "%scomment: |-\n", indent);
@ -209,7 +233,12 @@ void explainClass(struct mScriptTypeClass* cls, int level) {
case mSCRIPT_CLASS_INIT_INSTANCE_MEMBER:
fprintf(out, "%s %s:\n", indent, details->info.member.name);
if (docstring) {
fprintf(out, "%s comment: \"%s\"\n", indent, docstring);
if (strchr(docstring, '\n')) {
fprintf(out, "%s comment: |-\n", indent);
printchomp(docstring, level + 3);
} else {
fprintf(out, "%s comment: \"%s\"\n", indent, docstring);
}
docstring = NULL;
}
fprintf(out, "%s type: %s\n", indent, details->info.member.type->name);
@ -250,6 +279,10 @@ void explainObject(struct mScriptValue* value, int level) {
}
void explainValue(struct mScriptValue* value, const char* name, int level) {
explainValueScoped(value, name, NULL, level);
}
void explainValueScoped(struct mScriptValue* value, const char* name, const char* scope, int level) {
char valstring[1024];
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
@ -260,16 +293,26 @@ void explainValue(struct mScriptValue* value, const char* name, int level) {
const char* docstring = NULL;
if (name) {
docstring = mScriptContextGetDocstring(&context, name);
if (scope) {
snprintf(valstring, sizeof(valstring), "%s::%s", scope, name);
docstring = mScriptContextGetDocstring(&context, valstring);
} else {
docstring = mScriptContextGetDocstring(&context, name);
}
}
if (docstring) {
fprintf(out, "%scomment: \"%s\"\n", indent, docstring);
if (strchr(docstring, '\n')) {
fprintf(out, "%scomment: |-\n", indent);
printchomp(docstring, level + 1);
} else {
fprintf(out, "%scomment: \"%s\"\n", indent, docstring);
}
}
switch (value->type->base) {
case mSCRIPT_TYPE_TABLE:
fprintf(out, "%svalue:\n", indent);
explainTable(value, name, level);
explainTable(value, name, scope, level);
break;
case mSCRIPT_TYPE_SINT:
case mSCRIPT_TYPE_UINT:
@ -438,6 +481,35 @@ void explainCore(struct mCore* core) {
mScriptContextDetachCore(&context);
}
void initTypes(void) {
HashTableInit(&types, 0, NULL);
size_t i;
for (i = 0; baseTypes[i]; ++i) {
addType(baseTypes[i]);
}
}
void explainTypes(int level, const char* prefix) {
char indent[level * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
fprintf(out, "%stypes:\n", indent);
struct TableIterator iter;
if (HashTableIteratorStart(&types, &iter)) {
do {
const char* name = HashTableIteratorGetKey(&types, &iter);
struct mScriptType* type = HashTableIteratorGetValue(&types, &iter);
if (prefix && !startswith(name, prefix)) {
continue;
}
fprintf(out, "%s %s:\n", indent, name);
explainType(type, level + 1);
} while (HashTableIteratorNext(&types, &iter));
}
}
int main(int argc, char* argv[]) {
out = stdout;
if (argc > 1) {
@ -450,24 +522,10 @@ int main(int argc, char* argv[]) {
mScriptContextInit(&context);
mScriptContextAttachStdlib(&context);
mScriptContextAttachSocket(&context);
mScriptContextSetTextBufferFactory(&context, NULL, NULL);
HashTableInit(&types, 0, NULL);
addType(mSCRIPT_TYPE_MS_S8);
addType(mSCRIPT_TYPE_MS_U8);
addType(mSCRIPT_TYPE_MS_S16);
addType(mSCRIPT_TYPE_MS_U16);
addType(mSCRIPT_TYPE_MS_S32);
addType(mSCRIPT_TYPE_MS_U32);
addType(mSCRIPT_TYPE_MS_F32);
addType(mSCRIPT_TYPE_MS_S64);
addType(mSCRIPT_TYPE_MS_U64);
addType(mSCRIPT_TYPE_MS_F64);
addType(mSCRIPT_TYPE_MS_STR);
addType(mSCRIPT_TYPE_MS_CHARP);
addType(mSCRIPT_TYPE_MS_LIST);
addType(mSCRIPT_TYPE_MS_TABLE);
addType(mSCRIPT_TYPE_MS_WRAPPER);
initTypes();
fputs("version:\n", out);
fprintf(out, " string: \"%s\"\n", projectVersion);
@ -498,16 +556,28 @@ int main(int argc, char* argv[]) {
explainCore(core);
core->deinit(core);
}
fputs("types:\n", out);
if (HashTableIteratorStart(&types, &iter)) {
do {
const char* name = HashTableIteratorGetKey(&types, &iter);
fprintf(out, " %s:\n", name);
struct mScriptType* type = HashTableIteratorGetValue(&types, &iter);
explainType(type, 1);
} while (HashTableIteratorNext(&types, &iter));
}
explainTypes(0, NULL);
mScriptContextRegisterEngines(&context);
fputs("engines:\n", out);
if (HashTableIteratorStart(&context.engines, &iter)) {
do {
struct TableIterator rootIter;
struct mScriptEngineContext* engine = HashTableIteratorGetValue(&context.engines, &iter);
const char* name = HashTableIteratorGetKey(&context.engines, &iter);
fprintf(out, " %s:\n root:\n", name);
if (HashTableIteratorStart(&engine->docroot, &rootIter)) {
do {
const char* key = HashTableIteratorGetKey(&engine->docroot, &rootIter);
struct mScriptValue* value = HashTableIteratorGetValue(&engine->docroot, &rootIter);
fprintf(out, " %s:\n", key);
explainValueScoped(value, key, name, 3);
} while (HashTableIteratorNext(&engine->docroot, &rootIter));
}
explainTypes(2, name);
} while (HashTableIteratorNext(&context.engines, &iter));
}
HashTableDeinit(&types);
mScriptContextDeinit(&context);
return 0;

View File

@ -5,7 +5,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/script/lua.h>
#include <mgba/internal/script/socket.h>
#include <mgba/script/context.h>
#include <mgba/script/macros.h>
#include <mgba/script/types.h>
#include <mgba-util/string.h>
#include <lualib.h>
@ -16,6 +19,9 @@
#endif
#define MAX_KEY_SIZE 128
#define LUA_NAME "lua"
#define mSCRIPT_TYPE_MS_LUA_FUNC (&mSTLuaFunc)
static struct mScriptEngineContext* _luaCreate(struct mScriptEngine2*, struct mScriptContext*);
@ -23,6 +29,7 @@ static void _luaDestroy(struct mScriptEngineContext*);
static bool _luaIsScript(struct mScriptEngineContext*, const char*, struct VFile*);
static struct mScriptValue* _luaGetGlobal(struct mScriptEngineContext*, const char* name);
static bool _luaSetGlobal(struct mScriptEngineContext*, const char* name, struct mScriptValue*);
static struct mScriptValue* _luaRootScope(struct mScriptEngineContext*);
static bool _luaLoad(struct mScriptEngineContext*, const char*, struct VFile*);
static bool _luaRun(struct mScriptEngineContext*);
static const char* _luaGetError(struct mScriptEngineContext*);
@ -51,6 +58,157 @@ static int _luaLenList(lua_State* lua);
static int _luaRequireShim(lua_State* lua);
static const char* _socketLuaSource =
"socket = {\n"
" ERRORS = {},\n"
" tcp = function() return socket._create(_socket.create(), socket._tcpMT) end,\n"
" bind = function(address, port)\n"
" local s = socket.tcp()\n"
" local ok, err = s:bind(address, port)\n"
" if ok then return s end\n"
" return ok, err\n"
" end,\n"
" connect = function(address, port)\n"
" local s = socket.tcp()\n"
" local ok, err = s:connect(address, port)\n"
" if ok then return s end\n"
" return ok, err\n"
" end,\n"
" _create = function(sock, mt) return setmetatable({\n"
" _s = sock,\n"
" _callbacks = {},\n"
" _nextCallback = 1,\n"
" }, mt) end,\n"
" _wrap = function(status)\n"
" if status == 0 then return 1 end\n"
" return nil, socket.ERRORS[status] or ('error#' .. status)\n"
" end,\n"
" _mt = {\n"
" __index = {\n"
" close = function(self)\n"
" if self._onframecb then\n"
" callbacks:remove(self._onframecb)\n"
" self._onframecb = nil\n"
" end\n"
" self._callbacks = {}\n"
" return self._s:close()\n"
" end,\n"
" add = function(self, event, callback)\n"
" if not self._callbacks[event] then self._callbacks[event] = {} end\n"
" local cbid = self._nextCallback\n"
" self._nextCallback = cbid + 1\n"
" self._callbacks[event][cbid] = callback\n"
" return id\n"
" end,\n"
" remove = function(self, cbid)\n"
" for _, group in pairs(self._callbacks) do\n"
" if group[cbid] then\n"
" group[cbid] = nil\n"
" end\n"
" end\n"
" end,\n"
" _dispatch = function(self, event, ...)\n"
" if not self._callbacks[event] then return end\n"
" for k, cb in pairs(self._callbacks[event]) do\n"
" if cb then\n"
" local ok, ret = pcall(cb, self, ...)\n"
" if not ok then console:error(ret) end\n"
" end\n"
" end\n"
" end,\n"
" },\n"
" },\n"
" _tcpMT = {\n"
" __index = {\n"
" _hook = function(self, status)\n"
" if status == 0 then\n"
" self._onframecb = callbacks:add('frame', function() self:poll() end)\n"
" end\n"
" return socket._wrap(status)\n"
" end,\n"
" bind = function(self, address, port)\n"
" return socket._wrap(self._s:open(address or '', port))\n"
" end,\n"
" connect = function(self, address, port)\n"
" local status = self._s:connect(address, port)\n"
" return socket._wrap(status)\n"
" end,\n"
" listen = function(self, backlog)\n"
" local status = self._s:listen(backlog or 1)\n"
" return self:_hook(status)\n"
" end,\n"
" accept = function(self)\n"
" local client = self._s:accept()\n"
" if client.error ~= 0 then\n"
" client:close()\n"
" return socket._wrap(client.error)\n"
" end\n"
" local sock = socket._create(client, socket._tcpMT)\n"
" sock:_hook(0)\n"
" return sock\n"
" end,\n"
" send = function(self, data, i, j)\n"
" local result = self._s:send(string.sub(data, i or 1, j))\n"
" if result < 0 then return socket._wrap(self._s.error) end\n"
" if i then return result + i - 1 end\n"
" return result\n"
" end,\n"
// TODO: This does not match the API for LuaSocket's receive() implementation
" receive = function(self, maxBytes)\n"
" local result = self._s:recv(maxBytes)\n"
" if (not result or #result == 0) and self._s.error ~= 0 then\n"
" return socket._wrap(self._s.error)\n"
" elseif not result or #result == 0 then\n"
" return nil, 'disconnected'\n"
" end\n"
" return result or ''\n"
" end,\n"
" hasdata = function(self)\n"
" local status = self._s:select(0)\n"
" if status < 0 then\n"
" return socket._wrap(self._s.error)\n"
" end\n"
" return status > 0\n"
" end,\n"
" poll = function(self)\n"
" local status, err = self:hasdata()\n"
" if err then\n"
" self:_dispatch('error', err)\n"
" elseif status then\n"
" self:_dispatch('received')\n"
" end\n"
" end,\n"
" },\n"
" },\n"
" _errMT = {\n"
" __index = function (tbl, key)\n"
" return rawget(tbl, C.SOCKERR[key])\n"
" end,\n"
" },\n"
"}\n"
"setmetatable(socket._tcpMT.__index, socket._mt)\n"
"setmetatable(socket.ERRORS, socket._errMT)\n";
static const struct _mScriptSocketError {
enum mSocketErrorCode err;
const char* message;
} _mScriptSocketErrors[] = {
{ mSCRIPT_SOCKERR_UNKNOWN_ERROR, "unknown error" },
{ mSCRIPT_SOCKERR_OK, NULL },
{ mSCRIPT_SOCKERR_AGAIN, "temporary failure" },
{ mSCRIPT_SOCKERR_ADDRESS_IN_USE, "address in use" },
{ mSCRIPT_SOCKERR_DENIED, "access denied" },
{ mSCRIPT_SOCKERR_UNSUPPORTED, "unsupported" },
{ mSCRIPT_SOCKERR_CONNECTION_REFUSED, "connection refused" },
{ mSCRIPT_SOCKERR_NETWORK_UNREACHABLE, "network unreachable" },
{ mSCRIPT_SOCKERR_TIMEOUT, "timeout" },
{ mSCRIPT_SOCKERR_FAILED, "failed" },
{ mSCRIPT_SOCKERR_NOT_FOUND, "not found" },
{ mSCRIPT_SOCKERR_NO_DATA, "no data" },
{ mSCRIPT_SOCKERR_OUT_OF_MEMORY, "out of memory" },
};
static const int _mScriptSocketNumErrors = sizeof(_mScriptSocketErrors) / sizeof(struct _mScriptSocketError);
#if LUA_VERSION_NUM < 503
#define lua_pushinteger lua_pushnumber
#endif
@ -61,12 +219,87 @@ static int _luaRequireShim(lua_State* lua);
#if LUA_VERSION_NUM < 502
#define luaL_traceback(L, M, S, level) lua_pushstring(L, S)
#define lua_pushglobaltable(L) lua_pushvalue(L, LUA_GLOBALSINDEX)
#endif
const struct mScriptType mSTLuaFunc;
mSCRIPT_DECLARE_DOC_STRUCT(LUA_NAME, socket);
mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, S64, add, 2, STR, event, LUA_FUNC, callback);
mSCRIPT_DECLARE_DOC_STRUCT_VOID_METHOD(LUA_NAME, socket, remove, 1, S64, cbid);
mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, S32, bind, 2, STR, address, U16, port);
mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, S32, connect, 2, STR, address, U16, port);
mSCRIPT_DECLARE_DOC_STRUCT_METHOD_WITH_DEFAULTS(LUA_NAME, socket, S32, listen, 1, S32, backlog);
mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, DS(socket), accept, 0);
mSCRIPT_DECLARE_DOC_STRUCT_METHOD_WITH_DEFAULTS(LUA_NAME, socket, S32, send, 3, STR, data, S64, i, S64, j);
mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, STR, receive, 1, S64, maxBytes);
mSCRIPT_DECLARE_DOC_STRUCT_METHOD(LUA_NAME, socket, BOOL, hasdata, 0);
mSCRIPT_DECLARE_DOC_STRUCT_VOID_METHOD(LUA_NAME, socket, poll, 0);
mSCRIPT_DEFINE_DOC_STRUCT(LUA_NAME, socket)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
"An instance of a TCP socket. Most of these functions will return two values if an error occurs; "
"the first value is `nil` and the second value is an error string from socket.ERRORS"
)
mSCRIPT_DEFINE_DOCSTRING(
"Add a callback for a named event. The returned id can be used to remove it later. "
"Events get checked once per frame but can be checked manually using " LUA_NAME "::struct::socket.poll. "
"The following callbacks are defined:\n\n"
"- **received**: New data has been received and can be read\n"
"- **error**: An error has occurred on the socket\n"
)
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, add)
mSCRIPT_DEFINE_DOCSTRING("Remove a callback with the previously returned id")
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, remove)
mSCRIPT_DEFINE_DOCSTRING("Creates a new socket for an incoming connection from a listening server socket")
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, accept)
mSCRIPT_DEFINE_DOCSTRING("Bind the socket to a specific interface and port. Use `nil` for `address` to bind to all interfaces")
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, bind)
mSCRIPT_DEFINE_DOCSTRING(
"Opens a TCP connection to the specified address and port.\n\n"
"**Caution:** This is a blocking call. The emulator will not respond until "
"the connection either succeeds or fails"
)
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, connect)
mSCRIPT_DEFINE_DOCSTRING(
"Begins listening for incoming connections. The socket must have first been "
"bound with the " LUA_NAME "::struct::socket.bind function"
)
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, listen)
mSCRIPT_DEFINE_DOCSTRING(
"Writes a string to the socket. If `i` and `j` are provided, they have the same semantics "
"as the parameters to `string.sub` to write a substring. Returns the last index written"
)
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, send)
mSCRIPT_DEFINE_DOCSTRING(
"Read up to `maxBytes` bytes from the socket and return them. "
"If the socket has been disconnected or an error occurs, it will return `nil, error` instead"
)
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, receive)
mSCRIPT_DEFINE_DOCSTRING("Check if a socket has data ready to receive, and return true if so")
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, hasdata)
mSCRIPT_DEFINE_DOCSTRING("Manually check for events on this socket and dispatch associated callbacks")
mSCRIPT_DEFINE_DOC_STRUCT_METHOD(LUA_NAME, socket, poll)
mSCRIPT_DEFINE_END;
mSCRIPT_DEFINE_DOC_STRUCT_BINDING_DEFAULTS(LUA_NAME, socket, listen)
mSCRIPT_S32(1),
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_DOC_STRUCT_BINDING_DEFAULTS(LUA_NAME, socket, send)
mSCRIPT_NO_DEFAULT,
mSCRIPT_S64(0),
mSCRIPT_S64(0),
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_DOC_FUNCTION(LUA_NAME, socket_tcp, DS(socket), 0);
mSCRIPT_DEFINE_DOC_FUNCTION(LUA_NAME, socket_bind, DS(socket), 2, STR, address, U16, port);
mSCRIPT_DEFINE_DOC_FUNCTION(LUA_NAME, socket_connect, DS(socket), 2, STR, address, U16, port);
const struct mScriptType mSTLuaFunc = {
.base = mSCRIPT_TYPE_FUNCTION,
.size = 0,
.name = "lua-" LUA_VERSION_ONLY "::function",
.name = LUA_NAME "::function",
.details = {
.function = {
.parameters = {
@ -101,7 +334,7 @@ static struct mScriptEngineLua {
struct mScriptEngine2 d;
} _engineLua = {
.d = {
.name = "lua-" LUA_VERSION_ONLY,
.name = LUA_NAME,
.init = NULL,
.deinit = NULL,
.create = _luaCreate
@ -133,14 +366,15 @@ static const luaL_Reg _mSTList[] = {
};
struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mScriptContext* context) {
UNUSED(engine);
struct mScriptEngineContextLua* luaContext = calloc(1, sizeof(*luaContext));
luaContext->d = (struct mScriptEngineContext) {
.context = context,
.engine = engine,
.destroy = _luaDestroy,
.isScript = _luaIsScript,
.getGlobal = _luaGetGlobal,
.setGlobal = _luaSetGlobal,
.rootScope = _luaRootScope,
.load = _luaLoad,
.run = _luaRun,
.getError = _luaGetError
@ -177,6 +411,56 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS
lua_getglobal(luaContext->lua, "require");
luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX);
HashTableInit(&luaContext->d.docroot, 0, (void (*)(void*)) mScriptValueDeref);
int status = luaL_dostring(luaContext->lua, _socketLuaSource);
if (status) {
mLOG(SCRIPT, ERROR, "Error in dostring while initializing sockets: %s\n", lua_tostring(luaContext->lua, -1));
lua_pop(luaContext->lua, 1);
} else {
struct mScriptValue* errors = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
int i;
lua_getglobal(luaContext->lua, "socket");
lua_getfield(luaContext->lua, -1, "ERRORS");
for (i = 0; i < _mScriptSocketNumErrors; i++) {
const struct _mScriptSocketError* err = &_mScriptSocketErrors[i];
if (err->message) {
lua_pushstring(luaContext->lua, err->message);
struct mScriptValue* key = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32);
key->value.s32 = err->err;
struct mScriptValue* message = mScriptStringCreateFromASCII(err->message);
mScriptTableInsert(errors, key, message);
mScriptValueDeref(key);
mScriptValueDeref(message);
} else {
lua_pushnil(luaContext->lua);
}
lua_seti(luaContext->lua, -2, err->err);
}
lua_pop(luaContext->lua, 2);
mScriptEngineExportDocNamespace(&luaContext->d, "socket", (struct mScriptKVPair[]) {
mSCRIPT_KV_PAIR(ERRORS, errors),
mSCRIPT_KV_PAIR(tcp, mSCRIPT_VALUE_DOC_FUNCTION(socket_tcp)),
mSCRIPT_KV_PAIR(bind, mSCRIPT_VALUE_DOC_FUNCTION(socket_bind)),
mSCRIPT_KV_PAIR(connect, mSCRIPT_VALUE_DOC_FUNCTION(socket_connect)),
mSCRIPT_KV_SENTINEL
});
mScriptValueDeref(errors);
mScriptEngineSetDocstring(&luaContext->d, "socket", "A basic TCP socket library");
mScriptEngineSetDocstring(&luaContext->d, "socket.ERRORS",
"Error strings corresponding to the C.SOCKERR error codes, indexed both by name and by value");
mScriptEngineSetDocstring(&luaContext->d, "socket.tcp",
"Create a new TCP socket, for use with either " LUA_NAME "::struct::socket.bind or " LUA_NAME "::struct::socket.connect later");
mScriptEngineSetDocstring(&luaContext->d, "socket.bind",
"Create and bind a new socket to a specific interface and port. "
"Use `nil` for `address` to bind to all interfaces");
mScriptEngineSetDocstring(&luaContext->d, "socket.connect",
"Create and return a new TCP socket with a connection to the specified address and port.\n\n"
"**Caution:** This is a blocking call. The emulator will not respond until "
"the connection either succeeds or fails");
}
return &luaContext->d;
}
@ -193,6 +477,8 @@ void _luaDestroy(struct mScriptEngineContext* ctx) {
luaL_unref(luaContext->lua, LUA_REGISTRYINDEX, luaContext->require);
}
lua_close(luaContext->lua);
HashTableDeinit(&luaContext->d.docroot);
free(luaContext);
}
@ -222,6 +508,24 @@ bool _luaSetGlobal(struct mScriptEngineContext* ctx, const char* name, struct mS
return true;
}
struct mScriptValue* _luaRootScope(struct mScriptEngineContext* ctx) {
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) ctx;
struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST);
lua_pushglobaltable(luaContext->lua);
lua_pushnil(luaContext->lua);
while (lua_next(luaContext->lua, -2) != 0) {
struct mScriptValue* key;
lua_pop(luaContext->lua, 1);
key = _luaCoerce(luaContext, false);
mScriptValueWrap(key, mScriptListAppend(list->value.list));
}
lua_pop(luaContext->lua, 1);
return list;
}
struct mScriptValue* _luaCoerceFunction(struct mScriptEngineContextLua* luaContext) {
struct mScriptValue* value = mScriptValueAlloc(&mSTLuaFunc);
struct mScriptFunction* fn = calloc(1, sizeof(*fn));

281
src/script/socket.c Normal file
View File

@ -0,0 +1,281 @@
/* 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 <mgba/script/context.h>
#include <errno.h>
#include <mgba/internal/script/socket.h>
#include <mgba/script/macros.h>
#include <mgba-util/socket.h>
struct mScriptSocket {
Socket socket;
struct Address address;
int32_t error;
uint16_t port;
};
mSCRIPT_DECLARE_STRUCT(mScriptSocket);
static const struct _mScriptSocketErrorMapping {
int32_t nativeError;
enum mSocketErrorCode mappedError;
} _mScriptSocketErrorMappings[] = {
{ EAGAIN, mSCRIPT_SOCKERR_AGAIN },
{ EADDRINUSE, mSCRIPT_SOCKERR_ADDRESS_IN_USE },
{ ECONNREFUSED, mSCRIPT_SOCKERR_CONNECTION_REFUSED },
{ EACCES, mSCRIPT_SOCKERR_DENIED },
{ EPERM, mSCRIPT_SOCKERR_DENIED },
{ ENOTRECOVERABLE, mSCRIPT_SOCKERR_FAILED },
{ ENETUNREACH, mSCRIPT_SOCKERR_NETWORK_UNREACHABLE },
{ ETIMEDOUT, mSCRIPT_SOCKERR_TIMEOUT },
{ EINVAL, mSCRIPT_SOCKERR_UNSUPPORTED },
{ EPROTONOSUPPORT, mSCRIPT_SOCKERR_UNSUPPORTED },
#ifndef USE_GETHOSTBYNAME
#ifdef _WIN32
{ WSATRY_AGAIN, mSCRIPT_SOCKERR_AGAIN },
{ WSANO_RECOVERY, mSCRIPT_SOCKERR_FAILED },
{ WSANO_DATA, mSCRIPT_SOCKERR_NO_DATA },
{ WSAHOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND },
{ WSATYPE_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND },
{ WSA_NOT_ENOUGH_MEMORY, mSCRIPT_SOCKERR_OUT_OF_MEMORY },
{ WSAEAFNOSUPPORT, mSCRIPT_SOCKERR_UNSUPPORTED },
{ WSAEINVAL, mSCRIPT_SOCKERR_UNSUPPORTED },
{ WSAESOCKTNOSUPPORT, mSCRIPT_SOCKERR_UNSUPPORTED },
#else
{ EAI_AGAIN, mSCRIPT_SOCKERR_AGAIN },
{ EAI_FAIL, mSCRIPT_SOCKERR_FAILED },
#ifdef EAI_NODATA
{ EAI_NODATA, mSCRIPT_SOCKERR_NO_DATA },
#endif
{ EAI_NONAME, mSCRIPT_SOCKERR_NOT_FOUND },
{ EAI_MEMORY, mSCRIPT_SOCKERR_OUT_OF_MEMORY },
#endif
#else
{ -TRY_AGAIN, mSCRIPT_SOCKERR_AGAIN },
{ -NO_RECOVERY, mSCRIPT_SOCKERR_FAILED },
{ -NO_DATA, mSCRIPT_SOCKERR_NO_DATA },
{ -HOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND },
#endif
};
static const int _mScriptSocketNumErrorMappings = sizeof(_mScriptSocketErrorMappings) / sizeof(struct _mScriptSocketErrorMapping);
static void _mScriptSocketSetError(struct mScriptSocket* ssock, int32_t err) {
if (!err) {
ssock->error = mSCRIPT_SOCKERR_OK;
return;
}
int i;
for (i = 0; i < _mScriptSocketNumErrorMappings; i++) {
if (_mScriptSocketErrorMappings[i].nativeError == err) {
ssock->error = _mScriptSocketErrorMappings[i].mappedError;
return;
}
}
ssock->error = mSCRIPT_SOCKERR_UNKNOWN_ERROR;
}
static void _mScriptSocketSetSocket(struct mScriptSocket* ssock, Socket socket) {
if (SOCKET_FAILED(socket)) {
ssock->socket = INVALID_SOCKET;
_mScriptSocketSetError(ssock, SocketError());
} else {
ssock->socket = socket;
ssock->error = mSCRIPT_SOCKERR_OK;
}
}
struct mScriptValue* _mScriptSocketCreate() {
struct mScriptSocket client = {
.socket = INVALID_SOCKET,
.error = mSCRIPT_SOCKERR_OK,
.port = 0
};
struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptSocket));
result->value.opaque = calloc(1, sizeof(struct mScriptSocket));
*(struct mScriptSocket*) result->value.opaque = client;
result->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
return result;
}
void _mScriptSocketClose(struct mScriptSocket* ssock) {
if (!SOCKET_FAILED(ssock->socket)) {
SocketClose(ssock->socket);
}
}
struct mScriptValue* _mScriptSocketAccept(struct mScriptSocket* ssock) {
struct mScriptValue* value = _mScriptSocketCreate();
struct mScriptSocket* client = (struct mScriptSocket*) value->value.opaque;
_mScriptSocketSetSocket(client, SocketAccept(ssock->socket, &client->address));
if (!client->error) {
SocketSetBlocking(client->socket, false);
}
return value;
}
int32_t _mScriptSocketOpen(struct mScriptSocket* ssock, const char* addressStr, uint16_t port) {
struct Address* addr = NULL;
if (addressStr && addressStr[0]) {
int32_t err = SocketResolveHost(addressStr, &ssock->address);
if (err) {
_mScriptSocketSetError(ssock, err);
return err;
}
addr = &ssock->address;
}
ssock->port = port;
_mScriptSocketSetSocket(ssock, SocketOpenTCP(port, addr));
if (!ssock->error) {
SocketSetBlocking(ssock->socket, false);
}
return ssock->error;
}
int32_t _mScriptSocketConnect(struct mScriptSocket* ssock, const char* addressStr, uint16_t port) {
int32_t err = SocketResolveHost(addressStr, &ssock->address);
if (err) {
_mScriptSocketSetError(ssock, err);
return err;
}
ssock->port = port;
_mScriptSocketSetSocket(ssock, SocketConnectTCP(port, &ssock->address));
if (!ssock->error) {
SocketSetBlocking(ssock->socket, false);
}
return ssock->error;
}
int32_t _mScriptSocketListen(struct mScriptSocket* ssock, uint32_t queueLength) {
_mScriptSocketSetError(ssock, SocketListen(ssock->socket, queueLength));
return ssock->error;
}
int32_t _mScriptSocketSend(struct mScriptSocket* ssock, struct mScriptString* data) {
ssize_t written = SocketSend(ssock->socket, data->buffer, data->size);
if (written < 0) {
_mScriptSocketSetError(ssock, SocketError());
return -ssock->error;
}
ssock->error = mSCRIPT_SOCKERR_OK;
return written;
}
struct mScriptValue* _mScriptSocketRecv(struct mScriptSocket* ssock, uint32_t maxBytes) {
struct mScriptValue* value = mScriptStringCreateEmpty(maxBytes);
struct mScriptString* data = value->value.string;
ssize_t bytes = SocketRecv(ssock->socket, data->buffer, maxBytes);
if (bytes <= 0) {
data->size = 0;
_mScriptSocketSetError(ssock, SocketError());
} else {
data->size = bytes;
ssock->error = mSCRIPT_SOCKERR_OK;
}
return value;
}
// This works sufficiently well for a single socket, but it could be better.
// Ideally, all sockets would be tracked and selected on together for efficiency.
uint32_t _mScriptSocketSelectOne(struct mScriptSocket* ssock, int64_t timeoutMillis) {
Socket reads[] = { ssock->socket };
Socket errors[] = { ssock->socket };
int result = SocketPoll(1, reads, NULL, errors, timeoutMillis);
if (!result) {
return 0;
} else if (errors[0] != INVALID_SOCKET) {
_mScriptSocketSetError(ssock, SocketError());
return -1;
}
return 1;
}
mSCRIPT_BIND_FUNCTION(mScriptSocketCreate_Binding, W(mScriptSocket), _mScriptSocketCreate, 0);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptSocket, close, _mScriptSocketClose, 0);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, W(mScriptSocket), accept, _mScriptSocketAccept, 0);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, open, _mScriptSocketOpen, 2, CHARP, address, U16, port);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, connect, _mScriptSocketConnect, 2, CHARP, address, U16, port);
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptSocket, S32, listen, _mScriptSocketListen, 1, U32, queueLength);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, send, _mScriptSocketSend, 1, STR, data);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, WSTR, recv, _mScriptSocketRecv, 1, U32, maxBytes);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, select, _mScriptSocketSelectOne, 1, S64, timeoutMillis);
mSCRIPT_DEFINE_STRUCT(mScriptSocket)
mSCRIPT_DEFINE_INTERNAL
mSCRIPT_DEFINE_CLASS_DOCSTRING("An internal implementation of a TCP network socket.")
mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(mScriptSocket, close)
mSCRIPT_DEFINE_DOCSTRING("Closes the socket. If the socket is already closed, this function does nothing.")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, close)
mSCRIPT_DEFINE_DOCSTRING("Creates a new socket for an incoming connection from a listening server socket.")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, accept)
mSCRIPT_DEFINE_DOCSTRING(
"Binds the socket to a specified address and port. "
"If no address is specified, the socket is bound to all network interfaces."
)
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, open)
mSCRIPT_DEFINE_DOCSTRING(
"Opens a TCP connection to the specified address and port.\n"
"**Caution:** This is a blocking call. The emulator will not respond until "
"the connection either succeeds or fails."
)
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, connect)
mSCRIPT_DEFINE_DOCSTRING(
"Begins listening for incoming connections. The socket must have first been "
"bound with the struct::Socket.open method."
)
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, listen)
mSCRIPT_DEFINE_DOCSTRING(
"Sends data over a socket. Returns the number of bytes written, or -1 if an "
"error occurs."
)
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, send)
mSCRIPT_DEFINE_DOCSTRING(
"Reads data from a socket, up to the specified number of bytes. "
"If the socket has been disconnected, this function returns an empty string. "
"Use struct::Socket.select to test if data is available to be read."
)
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, recv)
mSCRIPT_DEFINE_DOCSTRING(
"Checks the status of the socket. "
"Returns 1 if data is available to be read. "
"Returns -1 if an error has occurred on the socket."
)
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, select)
mSCRIPT_DEFINE_DOCSTRING(
"One of the C.SOCKERR constants describing the last error on the socket."
)
mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptSocket, S32, error)
mSCRIPT_DEFINE_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptSocket, listen)
mSCRIPT_S32(1)
mSCRIPT_DEFINE_DEFAULTS_END;
void mScriptContextAttachSocket(struct mScriptContext* context) {
mScriptContextExportNamespace(context, "_socket", (struct mScriptKVPair[]) {
mSCRIPT_KV_PAIR(create, &mScriptSocketCreate_Binding),
mSCRIPT_KV_SENTINEL
});
mScriptContextSetDocstring(context, "_socket", "Basic TCP sockets library");
mScriptContextSetDocstring(context, "_socket.create", "Creates a new socket object");
mScriptContextExportConstants(context, "SOCKERR", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, UNKNOWN_ERROR),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, OK),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, AGAIN),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, ADDRESS_IN_USE),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, CONNECTION_REFUSED),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, DENIED),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, FAILED),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, NETWORK_UNREACHABLE),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, NOT_FOUND),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, NO_DATA),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, OUT_OF_MEMORY),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, TIMEOUT),
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, UNSUPPORTED),
mSCRIPT_KV_SENTINEL
});
}

View File

@ -259,6 +259,63 @@ M_TEST_DEFINE(setGlobal) {
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(rootScope) {
SETUP_LUA;
struct mScriptValue* baseline = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
struct mScriptValue* val;
val = lua->rootScope(lua);
assert_non_null(val);
assert_int_equal(val->type->base, mSCRIPT_TYPE_LIST);
struct mScriptValue one = mSCRIPT_MAKE_S32(1);
size_t i;
for (i = 0; i < mScriptListSize(val->value.list); ++i) {
struct mScriptValue* key = mScriptListGetPointer(val->value.list, i);
if (key->type->base == mSCRIPT_TYPE_WRAPPER) {
key = mScriptValueUnwrap(key);
}
mScriptTableInsert(baseline, key, &one);
}
mScriptValueDeref(val);
TEST_PROGRAM("foo = 1; bar = 2;");
bool fooFound = false;
bool barFound = false;
val = lua->rootScope(lua);
assert_non_null(val);
assert_int_equal(val->type->base, mSCRIPT_TYPE_LIST);
assert_int_equal(mScriptListSize(val->value.list), mScriptTableSize(baseline) + 2);
for (i = 0; i < mScriptListSize(val->value.list); ++i) {
struct mScriptValue* key = mScriptListGetPointer(val->value.list, i);
if (key->type->base == mSCRIPT_TYPE_WRAPPER) {
key = mScriptValueUnwrap(key);
}
if (mScriptTableLookup(baseline, key)) {
continue;
}
assert_int_equal(key->type->base, mSCRIPT_TYPE_STRING);
if (strncmp(key->value.string->buffer, "foo", key->value.string->size) == 0) {
fooFound = true;
}
if (strncmp(key->value.string->buffer, "bar", key->value.string->size) == 0) {
barFound = true;
}
}
mScriptValueDeref(val);
assert_true(fooFound);
assert_true(barFound);
mScriptValueDeref(baseline);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(callLuaFunc) {
SETUP_LUA;
@ -658,6 +715,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua,
cmocka_unit_test(runNop),
cmocka_unit_test(getGlobal),
cmocka_unit_test(setGlobal),
cmocka_unit_test(rootScope),
cmocka_unit_test(callLuaFunc),
cmocka_unit_test(callCFunc),
cmocka_unit_test(globalStructFieldGet),

View File

@ -266,10 +266,11 @@ void _allocList(struct mScriptValue* val) {
void _freeList(struct mScriptValue* val) {
size_t i;
for (i = 0; i < mScriptListSize(val->value.list); ++i) {
if (val->type) {
struct mScriptValue* value = mScriptListGetPointer(val->value.list, i);
if (!value->type) {
continue;
}
struct mScriptValue* unwrapped = mScriptValueUnwrap(mScriptListGetPointer(val->value.list, i));
struct mScriptValue* unwrapped = mScriptValueUnwrap(value);
if (unwrapped) {
mScriptValueDeref(unwrapped);
}
@ -308,7 +309,7 @@ static void _allocString(struct mScriptValue* val) {
static void _freeString(struct mScriptValue* val) {
struct mScriptString* string = val->value.string;
if (string->size) {
if (string->buffer) {
free(string->buffer);
}
free(string);
@ -1087,6 +1088,9 @@ static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScript
docstring = NULL;
}
break;
case mSCRIPT_CLASS_INIT_INTERNAL:
cls->internal = true;
break;
}
}
}

View File

@ -17,10 +17,6 @@ static void _printStatus(struct CLIDebuggerSystem*);
static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVector* dv);
static uint16_t _printLine(struct CLIDebugger* debugger, uint16_t address, int segment);
static struct CLIDebuggerCommandSummary _sm83Commands[] = {
{ 0, 0, 0, 0 }
};
static inline void _printFlags(struct CLIDebuggerBackend* be, union FlagRegister f) {
be->printf(be, "F: [%c%c%c%c]\n",
f.z ? 'Z' : '-',
@ -109,6 +105,6 @@ void SM83CLIDebuggerCreate(struct CLIDebuggerSystem* debugger) {
debugger->printStatus = _printStatus;
debugger->disassemble = _disassemble;
debugger->platformName = "SM83";
debugger->platformCommands = _sm83Commands;
debugger->platformCommands = NULL;
debugger->platformCommandAliases = NULL;
}

View File

@ -134,6 +134,9 @@ ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size) {
#ifdef _POSIX_MAPPED_FILES
static void* _vfdMap(struct VFile* vf, size_t size, int flags) {
struct VFileFD* vfd = (struct VFileFD*) vf;
if (!size) {
return NULL;
}
int mmapFlags = MAP_PRIVATE;
if (flags & MAP_WRITE) {
mmapFlags = MAP_SHARED;
@ -153,6 +156,9 @@ static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) {
#elif defined(_WIN32)
static void* _vfdMap(struct VFile* vf, size_t size, int flags) {
struct VFileFD* vfd = (struct VFileFD*) vf;
if (!size) {
return NULL;
}
int createFlags = PAGE_WRITECOPY;
int mapFiles = FILE_MAP_COPY;
if (flags & MAP_WRITE) {
@ -192,12 +198,16 @@ static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) {
#else
static void* _vfdMap(struct VFile* vf, size_t size, int flags) {
struct VFileFD* vfd = (struct VFileFD*) vf;
if (!size) {
vfd->writable = false;
return NULL;
}
if (flags & MAP_WRITE) {
vfd->writable = true;
}
void* mem = anonymousMemoryMap(size);
if (!mem) {
return 0;
return NULL;
}
off_t pos = lseek(vfd->fd, 0, SEEK_CUR);