mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
47952e3413
11
CHANGES
11
CHANGES
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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*);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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), \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
88
src/gb/gb.c
88
src/gb/gb.c
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
include(ExportDirectory)
|
||||
set(SOURCE_FILES
|
||||
context.c
|
||||
socket.c
|
||||
stdlib.c
|
||||
types.c)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue