Merge branch 'master' into translations

This commit is contained in:
Vicki Pfau 2022-10-10 00:03:55 -07:00
commit fe9acf2f81
409 changed files with 64321 additions and 59255 deletions

16
CHANGES
View File

@ -19,6 +19,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
@ -32,6 +33,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)
@ -56,10 +58,12 @@ Other fixes:
- Core: Fix the runloop resuming after a game has crashed (fixes mgba.io/i/2451)
- Core: Fix crash if library can't be opened
- Debugger: Fix crash with extremely long CLI strings
- Debugger: Fix multiple conditional watchpoints at the same address
- FFmpeg: Fix crash when encoding audio with some containers
- 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
@ -75,13 +79,18 @@ Misc:
- Debugger: Save and restore CLI history
- 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
- GB Video: Add default SGB border
- 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)
@ -100,8 +109,13 @@ Misc:
- Qt: Add e-Card passing to the command line (closes mgba.io/i/2474)
- Qt: Boot both a multiboot image and ROM with CLI args (closes mgba.io/i/1941)
- Qt: Improve cheat parsing (fixes mgba.io/i/2297)
- Qt: Change lossless setting to use WavPack audio
- Qt: Use FFmpeg to convert additional camera formats, if available
- Qt: Resume crashed game when loading a save state
- Qt: Include cheats in bug report
- SDL: Support exposing an axis directly as the gyro value (closes mgba.io/i/2531)
- Windows: Attach to console if present
- VFS: Early return NULL if attempting to map 0 bytes from a file
- Vita: Add bilinear filtering option (closes mgba.io/i/344)
0.9.3: (2021-12-17)

View File

@ -22,14 +22,11 @@ if(NOT LIBMGBA_ONLY)
set(BINARY_NAME ${BINARY_NAME} CACHE INTERNAL "Name of output binaries")
endif()
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD 11)
if(NOT MSVC)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
if(SWITCH OR 3DS)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_EXTENSIONS ON)
elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "4.3")
if(SWITCH OR 3DS OR (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "4.3"))
set(CMAKE_C_EXTENSIONS ON)
endif()
set(WARNING_FLAGS "-Wall -Wextra -Wno-missing-field-initializers")
@ -40,8 +37,8 @@ if(NOT MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-")
endif()
if(NOT LIBMGBA_ONLY)
@ -243,6 +240,13 @@ if(APPLE)
if(NOT CMAKE_SYSTEM_VERSION VERSION_LESS "10.0") # Darwin 10.x is Mac OS X 10.6
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6")
endif()
# Not supported until Xcode 9
if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "9")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D__STDC_NO_THREADS__=1")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__STDC_NO_THREADS__=1")
endif()
endif()
if(NOT HAIKU AND NOT MSVC AND NOT PSP2)
@ -862,6 +866,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)
@ -894,15 +902,15 @@ if(NOT SKIP_LIBRARY)
install(TARGETS ${BINARY_NAME} LIBRARY DESTINATION ${LIBDIR} COMPONENT ${BINARY_NAME}-dev NAMELINK_ONLY)
endif()
if(UNIX AND NOT APPLE AND NOT HAIKU)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-16.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-24.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-32.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/32x32/apps RENAME mgba.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME mgba.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME mgba.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-96.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/96x96/apps RENAME mgba.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME mgba.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME mgba.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-512.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/512x512/apps RENAME mgba.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-16.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/16x16/apps RENAME io.mgba.${PROJECT_NAME}.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-24.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/24x24/apps RENAME io.mgba.${PROJECT_NAME}.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-32.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/32x32/apps RENAME io.mgba.${PROJECT_NAME}.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME io.mgba.${PROJECT_NAME}.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME io.mgba.${PROJECT_NAME}.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-96.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/96x96/apps RENAME io.mgba.${PROJECT_NAME}.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME io.mgba.${PROJECT_NAME}.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME io.mgba.${PROJECT_NAME}.png COMPONENT ${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-512.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/512x512/apps RENAME io.mgba.${PROJECT_NAME}.png COMPONENT ${BINARY_NAME})
endif()
else()
set(BUILD_SHARED OFF)

View File

@ -65,7 +65,7 @@ The following mappers are partially supported:
- MBC6 (missing flash memory write support)
- MMM01
- Pocket Cam
- TAMA5 (missing RTC support)
- TAMA5 (incomplete RTC support)
- HuC-1 (missing IR support)
- HuC-3 (missing IR support)
- Sachen MMC2 (missing alternate wiring support)
@ -76,7 +76,6 @@ The following mappers are partially supported:
- Dolphin/JOY bus link cable support.
- MP2k audio mixing, for higher quality sound than hardware.
- Re-recording support for tool-assist runs.
- Lua support for scripting.
- A comprehensive debug suite.
- Wireless adapter support.
@ -118,7 +117,7 @@ Controls are configurable in the settings menu. Many game controllers should be
Compiling
---------
Compiling requires using CMake 3.1 or newer. GCC and Clang are both known to work to compile mGBA, but Visual Studio 2013 and older are known not to work. Support for Visual Studio 2015 and newer is coming soon.
Compiling requires using CMake 3.1 or newer. GCC, Clang, and Visual Studio 2019 are known to work for compiling mGBA.
#### Docker building

View File

@ -20,6 +20,7 @@
CXX_GUARD_START
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <inttypes.h>
@ -83,6 +84,10 @@ typedef intptr_t ssize_t;
#define M_PI 3.141592654f
#endif
#if !defined(__cplusplus) && !defined(static_assert)
#define static_assert(X, C) _Static_assert((X), C)
#endif
#if !defined(_MSC_VER) && (defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))
#define ATOMIC_STORE(DST, SRC) __atomic_store_n(&DST, SRC, __ATOMIC_RELEASE)
#define ATOMIC_LOAD(DST, SRC) DST = __atomic_load_n(&SRC, __ATOMIC_ACQUIRE)

View File

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

View File

@ -24,7 +24,6 @@ typedef THREAD_ENTRY (*ThreadEntry)(void*);
typedef pthread_t Thread;
typedef pthread_mutex_t Mutex;
typedef pthread_cond_t Condition;
typedef pthread_key_t ThreadLocal;
static inline int MutexInit(Mutex* mutex) {
return pthread_mutex_init(mutex, 0);
@ -104,6 +103,9 @@ static inline int ThreadSetName(const char* name) {
#endif
}
#if (__STDC_VERSION__ < 201112L) || (__STDC_NO_THREADS__ == 1)
typedef pthread_key_t ThreadLocal;
static inline void ThreadLocalInitKey(ThreadLocal* key) {
pthread_key_create(key, 0);
}
@ -115,6 +117,7 @@ static inline void ThreadLocalSetKey(ThreadLocal key, void* value) {
static inline void* ThreadLocalGetValue(ThreadLocal key) {
return pthread_getspecific(key);
}
#endif
CXX_GUARD_END

View File

@ -17,7 +17,6 @@ typedef struct {
} Condition;
#define THREAD_ENTRY int
typedef THREAD_ENTRY (*ThreadEntry)(void*);
typedef int ThreadLocal;
static inline int MutexInit(Mutex* mutex) {
Mutex id = sceKernelCreateMutex("mutex", 0, 0, 0);
@ -145,6 +144,9 @@ static inline int ThreadSetName(const char* name) {
return -1;
}
#if (__STDC_VERSION__ < 201112L) || (__STDC_NO_THREADS__ == 1)
typedef int ThreadLocal;
static inline void ThreadLocalInitKey(ThreadLocal* key) {
static int base = 0x90;
*key = __atomic_fetch_add(&base, 1, __ATOMIC_SEQ_CST);
@ -160,3 +162,4 @@ static inline void* ThreadLocalGetValue(ThreadLocal key) {
return *tls;
}
#endif
#endif

View File

@ -16,7 +16,6 @@ typedef THREAD_ENTRY ThreadEntry(LPVOID);
typedef HANDLE Thread;
typedef CRITICAL_SECTION Mutex;
typedef CONDITION_VARIABLE Condition;
typedef DWORD ThreadLocal;
static inline int MutexInit(Mutex* mutex) {
InitializeCriticalSection(mutex);
@ -89,6 +88,9 @@ static inline int ThreadSetName(const char* name) {
return -1;
}
#if (__STDC_VERSION__ < 201112L) || (__STDC_NO_THREADS__ == 1)
typedef DWORD ThreadLocal;
static inline void ThreadLocalInitKey(ThreadLocal* key) {
*key = TlsAlloc();
}
@ -100,5 +102,6 @@ static inline void ThreadLocalSetKey(ThreadLocal key, void* value) {
static inline void* ThreadLocalGetValue(ThreadLocal key) {
return TlsGetValue(key);
}
#endif
#endif

View File

@ -21,11 +21,14 @@ CXX_GUARD_START
typedef SOCKET Socket;
#else
#ifdef GEKKO
#define USE_GETHOSTBYNAME
#include <network.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#endif
#include <errno.h>
@ -39,6 +42,10 @@ typedef SOCKET Socket;
typedef int Socket;
#endif
#if !defined(__3DS__) && !defined(GEKKO)
#define HAS_IPV6
#endif
enum IP {
IPV4,
IPV6
@ -152,17 +159,54 @@ static inline int SocketClose(Socket socket) {
#endif
}
static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress) {
#ifdef GEKKO
Socket sock = net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
static inline void SocketCloseQuiet(Socket socket) {
int savedErrno = SocketError();
SocketClose(socket);
#ifdef _WIN32
WSASetLastError(savedErrno);
#else
Socket sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
errno = savedErrno;
#endif
}
static inline Socket SocketCreate(bool useIPv6, int protocol) {
if (useIPv6) {
#ifdef HAS_IPV6
return socket(AF_INET6, SOCK_STREAM, protocol);
#else
errno = EAFNOSUPPORT;
return INVALID_SOCKET;
#endif
} else {
#ifdef GEKKO
return net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
#else
return socket(AF_INET, SOCK_STREAM, protocol);
#endif
}
}
static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress) {
bool useIPv6 = bindAddress && (bindAddress->version == IPV6);
Socket sock = SocketCreate(useIPv6, IPPROTO_TCP);
if (SOCKET_FAILED(sock)) {
return sock;
}
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));
@ -178,7 +222,7 @@ static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress)
#else
err = bind(sock, (const struct sockaddr*) &bindInfo, sizeof(bindInfo));
#endif
} else if (bindAddress->version == IPV4) {
} else if (!useIPv6) {
struct sockaddr_in bindInfo;
memset(&bindInfo, 0, sizeof(bindInfo));
bindInfo.sin_family = AF_INET;
@ -200,18 +244,15 @@ static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress)
#endif
}
if (err) {
SocketClose(sock);
SocketCloseQuiet(sock);
return INVALID_SOCKET;
}
return sock;
}
static inline Socket SocketConnectTCP(int port, const struct Address* destinationAddress) {
#ifdef GEKKO
Socket sock = net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
#else
Socket sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif
bool useIPv6 = destinationAddress && (destinationAddress->version == IPV6);
Socket sock = SocketCreate(useIPv6, IPPROTO_TCP);
if (SOCKET_FAILED(sock)) {
return sock;
}
@ -238,7 +279,7 @@ static inline Socket SocketConnectTCP(int port, const struct Address* destinatio
#else
err = connect(sock, (const struct sockaddr*) &bindInfo, sizeof(bindInfo));
#endif
#if !defined(__3DS__) && !defined(GEKKO)
#ifdef HAS_IPV6
} else {
struct sockaddr_in6 bindInfo;
memset(&bindInfo, 0, sizeof(bindInfo));
@ -250,7 +291,7 @@ static inline Socket SocketConnectTCP(int port, const struct Address* destinatio
}
if (err) {
SocketClose(sock);
SocketCloseQuiet(sock);
return INVALID_SOCKET;
}
return sock;
@ -413,6 +454,73 @@ static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Soc
return result;
}
static inline int SocketResolveHost(const char* addrString, struct Address* destAddress) {
int result = 0;
#ifdef USE_GETHOSTBYNAME
#warning Using gethostbyname() for hostname resolution is not threadsafe
#ifdef GEKKO
struct hostent* host = net_gethostbyname(addrString);
#else
struct hostent* host = gethostbyname(addrString);
#endif
if (!host) {
return errno;
}
if (host->h_addrtype == AF_INET && host->h_length == 4) {
destAddress->version = IPV4;
destAddress->ipv4 = ntohl(*host->h_addr_list[0]);
}
#ifdef HAS_IPV6
else if (host->h_addrtype == AF_INET6 && host->h_length == 16) {
destAddress->version = IPV6;
memcpy(destAddress->ipv6, host->h_addr_list[0], 16);
}
#endif
else {
#ifdef GEKKO
result = errno;
#else
result = -h_errno;
#endif
}
#else
struct addrinfo* addr = NULL;
result = getaddrinfo(addrString, NULL, NULL, &addr);
if (result) {
#ifdef EAI_SYSTEM
if (result == EAI_SYSTEM) {
result = errno;
}
#endif
goto error;
}
if (addr->ai_family == AF_INET && addr->ai_addrlen == sizeof(struct sockaddr_in)) {
struct sockaddr_in* addr4 = (struct sockaddr_in*) addr->ai_addr;
destAddress->version = IPV4;
destAddress->ipv4 = ntohl(addr4->sin_addr.s_addr);
}
#ifdef HAS_IPV6
else if (addr->ai_family == AF_INET6 && addr->ai_addrlen == sizeof(struct sockaddr_in6)) {
struct sockaddr_in6* addr6 = (struct sockaddr_in6*) addr->ai_addr;
destAddress->version = IPV6;
memcpy(destAddress->ipv6, addr6->sin6_addr.s6_addr, 16);
}
#endif
else {
#ifdef _WIN32
result = WSANO_DATA;
#else
result = EAI_NONAME;
#endif
}
error:
if (addr) {
freeaddrinfo(addr);
}
#endif
return result;
}
CXX_GUARD_END
#endif

View File

@ -11,12 +11,6 @@
CXX_GUARD_START
#ifndef DISABLE_THREADING
#if (__STDC_VERSION__ >= 201112L) && (__STDC_NO_THREADS__ != 1)
#define ThreadLocal _Thread_local void*
#define ThreadLocalInitKey(X)
#define ThreadLocalSetKey(K, V) K = V
#define ThreadLocalGetValue(K) K
#endif
#ifdef USE_PTHREADS
#include <mgba-util/platform/posix/threading.h>
#elif defined(_WIN32)
@ -31,7 +25,15 @@ CXX_GUARD_START
#define DISABLE_THREADING
#endif
#endif
#ifdef DISABLE_THREADING
#ifndef DISABLE_THREADING
#if (__STDC_VERSION__ >= 201112L) && (__STDC_NO_THREADS__ != 1)
#define ThreadLocal _Thread_local void*
#define ThreadLocalInitKey(X)
#define ThreadLocalSetKey(K, V) K = V
#define ThreadLocalGetValue(K) K
#endif
#else
#ifdef __3DS__
// ctrulib already has a type called Thread
#include <3ds/thread.h>

View File

@ -189,6 +189,7 @@ DECLARE_VECTOR(mCoreCallbacksList, struct mCoreCallbacks);
struct mAVStream {
void (*videoDimensionsChanged)(struct mAVStream*, unsigned width, unsigned height);
void (*audioRateChanged)(struct mAVStream*, unsigned rate);
void (*postVideoFrame)(struct mAVStream*, const color_t* buffer, size_t stride);
void (*postAudioFrame)(struct mAVStream*, int16_t left, int16_t right);
void (*postAudioBuffer)(struct mAVStream*, struct blip_t* left, struct blip_t* right);

View File

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

View File

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

View File

@ -33,14 +33,20 @@ struct mTiming {
void mTimingInit(struct mTiming* timing, int32_t* relativeCycles, int32_t* nextEvent);
void mTimingDeinit(struct mTiming* timing);
void mTimingClear(struct mTiming* timing);
void mTimingInterrupt(struct mTiming* timing);
void mTimingSchedule(struct mTiming* timing, struct mTimingEvent*, int32_t when);
void mTimingScheduleAbsolute(struct mTiming* timing, struct mTimingEvent*, int32_t when);
void mTimingDeschedule(struct mTiming* timing, struct mTimingEvent*);
bool mTimingIsScheduled(const struct mTiming* timing, const struct mTimingEvent*);
int32_t mTimingTick(struct mTiming* timing, int32_t cycles);
int32_t mTimingCurrentTime(const struct mTiming* timing);
uint64_t mTimingGlobalTime(const struct mTiming* timing);
int32_t mTimingNextEvent(struct mTiming* timing);
int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent*);

View File

@ -10,8 +10,11 @@
CXX_GUARD_START
#include <mgba/core/interface.h>
#include <mgba/core/timing.h>
#define GB_MAX_SAMPLES 32
DECL_BITFIELD(GBAudioRegisterDuty, uint8_t);
DECL_BITS(GBAudioRegisterDuty, Length, 0, 6);
DECL_BITS(GBAudioRegisterDuty, Duty, 6, 2);
@ -195,6 +198,10 @@ struct GBAudio {
int32_t sampleInterval;
enum GBAudioStyle style;
int32_t lastSample;
int sampleIndex;
struct mStereoSample currentSamples[GB_MAX_SAMPLES];
struct mTimingEvent frameEvent;
struct mTimingEvent sampleEvent;
bool enable;

View File

@ -50,12 +50,23 @@ struct GBMBCHuC3SaveBuffer {
uint64_t latchedUnix;
};
struct GBMBCTAMA5SaveBuffer {
uint8_t rtcTimerPage[0x8];
uint8_t rtcAlarmPage[0x8];
uint8_t rtcFreePage0[0x8];
uint8_t rtcFreePage1[0x8];
uint64_t latchedUnix;
};
void GBMBCRTCRead(struct GB* gb);
void GBMBCRTCWrite(struct GB* gb);
void GBMBCHuC3Read(struct GB* gb);
void GBMBCHuC3Write(struct GB* gb);
void GBMBCTAMA5Read(struct GB* gb);
void GBMBCTAMA5Write(struct GB* gb);
CXX_GUARD_END
#endif

View File

@ -94,7 +94,7 @@ enum GBTAMA5Register {
GBTAMA5_BANK_HI = 0x1,
GBTAMA5_WRITE_LO = 0x4,
GBTAMA5_WRITE_HI = 0x5,
GBTAMA5_CS = 0x6,
GBTAMA5_ADDR_HI = 0x6,
GBTAMA5_ADDR_LO = 0x7,
GBTAMA5_MAX = 0x8,
GBTAMA5_ACTIVE = 0xA,
@ -102,6 +102,46 @@ enum GBTAMA5Register {
GBTAMA5_READ_HI = 0xD,
};
enum GBTAMA6RTCRegister {
GBTAMA6_RTC_PA0_SECOND_1 = 0x0,
GBTAMA6_RTC_PA0_SECOND_10 = 0x1,
GBTAMA6_RTC_PA0_MINUTE_1 = 0x2,
GBTAMA6_RTC_PA0_MINUTE_10 = 0x3,
GBTAMA6_RTC_PA0_HOUR_1 = 0x4,
GBTAMA6_RTC_PA0_HOUR_10 = 0x5,
GBTAMA6_RTC_PA0_WEEK = 0x6,
GBTAMA6_RTC_PA0_DAY_1 = 0x7,
GBTAMA6_RTC_PA0_DAY_10 = 0x8,
GBTAMA6_RTC_PA0_MONTH_1 = 0x9,
GBTAMA6_RTC_PA0_MONTH_10 = 0xA,
GBTAMA6_RTC_PA0_YEAR_1 = 0xB,
GBTAMA6_RTC_PA0_YEAR_10 = 0xC,
GBTAMA6_RTC_PA1_MINUTE_1 = 0x2,
GBTAMA6_RTC_PA1_MINUTE_10 = 0x3,
GBTAMA6_RTC_PA1_HOUR_1 = 0x4,
GBTAMA6_RTC_PA1_HOUR_10 = 0x5,
GBTAMA6_RTC_PA1_WEEK = 0x6,
GBTAMA6_RTC_PA1_DAY_1 = 0x7,
GBTAMA6_RTC_PA1_DAY_10 = 0x8,
GBTAMA6_RTC_PA1_24_HOUR = 0xA,
GBTAMA6_RTC_PA1_LEAP_YEAR = 0xB,
GBTAMA6_RTC_PAGE = 0xD,
GBTAMA6_RTC_TEST = 0xE,
GBTAMA6_RTC_RESET = 0xF,
GBTAMA6_RTC_MAX
};
enum GBTAMA6Command {
GBTAMA6_DISABLE_TIMER = 0x0,
GBTAMA6_ENABLE_TIMER = 0x1,
GBTAMA6_MINUTE_WRITE = 0x4,
GBTAMA6_HOUR_WRITE = 0x5,
GBTAMA6_MINUTE_READ = 0x6,
GBTAMA6_HOUR_READ = 0x7,
GBTAMA6_DISABLE_ALARM = 0x10,
GBTAMA6_ENABLE_ALARM = 0x11,
};
enum GBHuC3Register {
GBHUC3_RTC_MINUTES_LO = 0x10,
GBHUC3_RTC_MINUTES_MI = 0x11,
@ -179,7 +219,12 @@ struct GBPocketCamState {
struct GBTAMA5State {
uint8_t reg;
bool disabled;
uint8_t registers[GBTAMA5_MAX];
uint8_t rtcTimerPage[GBTAMA6_RTC_MAX];
uint8_t rtcAlarmPage[GBTAMA6_RTC_MAX];
uint8_t rtcFreePage0[GBTAMA6_RTC_MAX];
uint8_t rtcFreePage1[GBTAMA6_RTC_MAX];
};
struct GBHuC3State {

View File

@ -164,7 +164,12 @@ mLOG_DECLARE_CATEGORY(GB_STATE);
* | 0x00197: Reserved (leave zero)
* 0x00198 - 0x0019F: Global cycle counter
* 0x001A0 - 0x001A1: Program counter for last cartridge read
* 0x001A2 - 0x0025F: Reserved (leave zero)
* 0x001A2 - 0x001D7: Reserved (leave zero)
* 0x001D8 - 0x0025F: Additional audio state
* | 0x001D8 - 0x001DB: Last sample timestamp
* | 0x001DC: Current audio sample index
* | 0x001DD - 0x001DF: Reserved (leave zero)
* | 0x001E0 - 0x0025F: Audio rendered samples
* 0x00260 - 0x002FF: OAM
* 0x00300 - 0x0037F: I/O memory
* 0x00380 - 0x003FE: HRAM
@ -401,6 +406,10 @@ struct GBSerializedState {
uint8_t locked;
uint8_t bank0;
} mmm01;
struct {
uint64_t lastLatch;
uint8_t reg;
} tama5;
struct {
uint64_t lastLatch;
uint8_t index;
@ -430,7 +439,15 @@ struct GBSerializedState {
uint64_t globalCycles;
uint16_t cartBusPc;
uint16_t reserved[95];
uint16_t reserved[27];
struct {
int32_t lastSample;
uint8_t sampleIndex;
uint8_t reserved[3];
struct mStereoSample currentSamples[GB_MAX_SAMPLES];
} audio2;
uint8_t oam[GB_SIZE_OAM];
@ -443,7 +460,17 @@ struct GBSerializedState {
uint32_t reserved2[0xA4];
uint8_t huc3Registers[0x80];
union {
uint8_t huc3Registers[0x80];
struct {
uint8_t registers[4];
uint8_t reserved[4];
uint8_t rtcTimerPage[8];
uint8_t rtcAlarmPage[8];
uint8_t rtcFreePage0[8];
uint8_t rtcFreePage1[8];
} tama5Registers;
};
struct {
uint8_t attributes[90];
@ -460,6 +487,8 @@ struct GBSerializedState {
};
#pragma pack(pop)
static_assert(sizeof(struct GBSerializedState) == 0x11800, "GB savestate struct sized wrong");
bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state);
void GBSerialize(struct GB* gb, struct GBSerializedState* state);

View File

@ -42,7 +42,7 @@ enum GPIODirection {
GPIO_READ_WRITE = 1
};
DECL_BITFIELD(RTCControl, uint32_t);
DECL_BITFIELD(RTCControl, uint8_t);
DECL_BIT(RTCControl, MinIRQ, 3);
DECL_BIT(RTCControl, Hour24, 6);
DECL_BIT(RTCControl, Poweroff, 7);
@ -69,6 +69,8 @@ struct GBARTC {
RTCCommandData command;
RTCControl control;
uint8_t time[7];
time_t lastLatch;
time_t offset;
};
DECL_BITFIELD(GPIOPin, uint16_t);

View File

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

View File

@ -348,7 +348,8 @@ struct GBASerializedState {
int32_t rtcBits;
int32_t rtcCommandActive;
RTCCommandData rtcCommand;
RTCControl rtcControl;
uint8_t rtcControl;
uint8_t reserved[3];
uint8_t time[7];
uint8_t devices;
uint16_t gyroSample;
@ -402,7 +403,7 @@ struct GBASerializedState {
int8_t chB[16];
} samples;
struct mStereoSample currentSamples[16];
struct mStereoSample currentSamples[GBA_MAX_SAMPLES];
uint32_t reserved[12];
@ -414,6 +415,8 @@ struct GBASerializedState {
uint8_t wram[SIZE_WORKING_RAM];
};
static_assert(sizeof(struct GBASerializedState) == 0x61000, "GBA savestate struct sized wrong");
struct VDir;
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);

View File

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

View File

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

View File

@ -84,7 +84,9 @@ CXX_GUARD_START
extern const struct mScriptType mSTStruct_ ## STRUCT; \
extern const struct mScriptType mSTStructConst_ ## STRUCT; \
extern const struct mScriptType mSTStructPtr_ ## STRUCT; \
extern const struct mScriptType mSTStructPtrConst_ ## STRUCT;
extern const struct mScriptType mSTStructPtrConst_ ## STRUCT; \
extern const struct mScriptType mSTWrapper_ ## STRUCT; \
extern const struct mScriptType mSTWrapperConst_ ## STRUCT;
#define mSCRIPT_DEFINE_STRUCT(STRUCT) \
const struct mScriptType mSTStruct_ ## STRUCT; \
@ -180,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 = { \
@ -214,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)) { \
@ -336,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) { \
@ -364,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 = { \
@ -391,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, \
@ -431,7 +494,40 @@ CXX_GUARD_START
_mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \
return true; \
} \
_mSCRIPT_BIND_FUNCTION(NAME, 0, , NPARAMS, __VA_ARGS__)
_mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NPARAMS, __VA_ARGS__)
#define _mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, NRET, RETURN, NPARAMS, ...) \
static const struct mScriptType _mScriptDocType_ ## NAME = { \
.base = mSCRIPT_TYPE_FUNCTION, \
.name = SCOPE "::function::" #NAME, \
.alloc = NULL, \
.details = { \
.function = { \
.parameters = { \
.count = NPARAMS, \
.entries = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \
.names = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \
}, \
.returnType = { \
.count = NRET, \
.entries = { RETURN } \
}, \
}, \
} \
}; \
const struct mScriptValue _mScriptDoc_ ## NAME = { \
.type = &_mScriptDocType_ ## NAME, \
.refs = mSCRIPT_VALUE_UNREF, \
.value = { \
.copaque = NULL \
} \
}
#define mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, RETURN, NPARAMS, ...) \
_mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DEFINE_DOC_VOID_FUNCTION(SCOPE, NAME, NPARAMS, ...) \
_mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, 0, 0, NPARAMS, __VA_ARGS__)
#define mSCRIPT_MAKE(TYPE, VALUE) (struct mScriptValue) { \
.type = (mSCRIPT_TYPE_MS_ ## TYPE), \

View File

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

View File

@ -1,6 +1,6 @@
[Desktop Entry]
Version=1.0
Icon=mgba
Icon=io.mgba.mGBA
Exec=mgba-qt %f
Terminal=false
Type=Application

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -0,0 +1,25 @@
[shader]
name=TV Mode
author=Dominus Iniquitatis
description=Scanlines along with a subtle blurring.
passes=1
[pass.0]
fragmentShader=tv.fs
blend=1
width=-2
height=-2
[pass.0.uniform.lineBrightness]
type=float
readableName=Line brightness
default=0.75
min=0.0
max=1.0
[pass.0.uniform.blurring]
type=float
readableName=Blurring
default=1.0
min=0.0
max=1.0

View File

@ -0,0 +1,44 @@
/*
TV Mode Shader
Copyright (C) 2022 Dominus Iniquitatis - zerosaiko@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
uniform sampler2D tex;
uniform vec2 texSize;
varying vec2 texCoord;
uniform float lineBrightness;
uniform float blurring;
void main()
{
vec4 c = texture2D(tex, texCoord);
vec4 n = texture2D(tex, texCoord + vec2(1.0 / texSize.x * 0.5, 0.0));
vec4 color = mix(c, (c + n) / 2.0, blurring);
color.a = c.a;
if (int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0)
{
color.rgb *= lineBrightness;
}
gl_FragColor = color;
}

View File

@ -102,7 +102,7 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st
int32_t value;
int segment;
if (!mDebuggerEvaluateParseTree(debugger->d.p, watchpoint->condition, &value, &segment) || !(value || segment >= 0)) {
return false;
continue;
}
}

View File

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

View File

@ -717,7 +717,7 @@ static void mScriptConsoleLog(struct mScriptConsole* console, struct mScriptStri
if (console->logger) {
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg->buffer);
} else {
mLog(_mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg->buffer);
mLog(_mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg->buffer);
}
}
@ -725,7 +725,7 @@ static void mScriptConsoleWarn(struct mScriptConsole* console, struct mScriptStr
if (console->logger) {
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
} else {
mLog(_mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
mLog(_mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
}
}
@ -733,7 +733,7 @@ static void mScriptConsoleError(struct mScriptConsole* console, struct mScriptSt
if (console->logger) {
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_ERROR, "%s", msg->buffer);
} else {
mLog(_mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
mLog(_mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
}
}
@ -818,7 +818,7 @@ mSCRIPT_DEFINE_STRUCT(mScriptTextBuffer)
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, clear)
mSCRIPT_DEFINE_DOCSTRING("Set the number of rows and columns")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, setSize)
mSCRIPT_DEFINE_DOCSTRING("Set the posiiton of the cursor")
mSCRIPT_DEFINE_DOCSTRING("Set the position of the cursor")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, moveCursor)
mSCRIPT_DEFINE_DOCSTRING("Advance the cursor a number of columns")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, advance)

View File

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

View File

@ -25,6 +25,14 @@ void mTimingClear(struct mTiming* timing) {
timing->masterCycles = 0;
}
void mTimingInterrupt(struct mTiming* timing) {
if (!timing->root) {
return;
}
timing->reroot = timing->root;
timing->root = NULL;
}
void mTimingSchedule(struct mTiming* timing, struct mTimingEvent* event, int32_t when) {
int32_t nextEvent = when + *timing->relativeCycles;
event->when = nextEvent + timing->masterCycles;

View File

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

View File

@ -37,12 +37,15 @@
static void _ffmpegPostVideoFrame(struct mAVStream*, const color_t* pixels, size_t stride);
static void _ffmpegPostAudioFrame(struct mAVStream*, int16_t left, int16_t right);
static void _ffmpegSetVideoDimensions(struct mAVStream*, unsigned width, unsigned height);
static void _ffmpegSetAudioRate(struct mAVStream*, unsigned rate);
static bool _ffmpegWriteAudioFrame(struct FFmpegEncoder* encoder, struct AVFrame* audioFrame);
static bool _ffmpegWriteVideoFrame(struct FFmpegEncoder* encoder, struct AVFrame* videoFrame);
static void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder);
enum {
PREFERRED_SAMPLE_RATE = 0x8000
PREFERRED_SAMPLE_RATE = 0x10000
};
void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
@ -51,9 +54,10 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
#endif
encoder->d.videoDimensionsChanged = _ffmpegSetVideoDimensions;
encoder->d.audioRateChanged = _ffmpegSetAudioRate;
encoder->d.postVideoFrame = _ffmpegPostVideoFrame;
encoder->d.postAudioFrame = _ffmpegPostAudioFrame;
encoder->d.postAudioBuffer = 0;
encoder->d.postAudioBuffer = NULL;
encoder->audioCodec = NULL;
encoder->videoCodec = NULL;
@ -64,6 +68,7 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
FFmpegEncoderSetDimensions(encoder, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS;
encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS;
encoder->isampleRate = PREFERRED_SAMPLE_RATE;
encoder->frameskip = 1;
encoder->skipResidue = 0;
encoder->loop = false;
@ -147,19 +152,24 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un
if (encoder->sampleFormat == AV_SAMPLE_FMT_NONE) {
return false;
}
encoder->sampleRate = PREFERRED_SAMPLE_RATE;
encoder->sampleRate = encoder->isampleRate;
if (codec->supported_samplerates) {
for (i = 0; codec->supported_samplerates[i]; ++i) {
if (codec->supported_samplerates[i] < PREFERRED_SAMPLE_RATE) {
if (codec->supported_samplerates[i] < encoder->isampleRate) {
continue;
}
if (encoder->sampleRate == PREFERRED_SAMPLE_RATE || encoder->sampleRate > codec->supported_samplerates[i]) {
if (encoder->sampleRate == encoder->isampleRate || encoder->sampleRate > codec->supported_samplerates[i]) {
encoder->sampleRate = codec->supported_samplerates[i];
}
}
} else if (codec->id == AV_CODEC_ID_FLAC) {
// HACK: FLAC doesn't support > 65535Hz unless it's divisible by 10
if (encoder->sampleRate >= 65535) {
encoder->sampleRate -= encoder->isampleRate % 10;
}
} else if (codec->id == AV_CODEC_ID_AAC) {
// HACK: AAC doesn't support 32768Hz (it rounds to 32000), but libfaac doesn't tell us that
encoder->sampleRate = 44100;
encoder->sampleRate = 48000;
}
encoder->audioCodec = acodec;
encoder->audioBitrate = abr;
@ -321,22 +331,7 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
encoder->audioFrame->format = encoder->audio->sample_fmt;
encoder->audioFrame->pts = 0;
encoder->audioFrame->channel_layout = AV_CH_LAYOUT_STEREO;
#ifdef USE_LIBAVRESAMPLE
encoder->resampleContext = avresample_alloc_context();
av_opt_set_int(encoder->resampleContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(encoder->resampleContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(encoder->resampleContext, "in_sample_rate", PREFERRED_SAMPLE_RATE, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_rate", encoder->sampleRate, 0);
av_opt_set_int(encoder->resampleContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0);
avresample_open(encoder->resampleContext);
#else
encoder->resampleContext = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, encoder->sampleFormat, encoder->sampleRate,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, PREFERRED_SAMPLE_RATE, 0, NULL);
swr_init(encoder->resampleContext);
#endif
encoder->audioBufferSize = (encoder->audioFrame->nb_samples * PREFERRED_SAMPLE_RATE / encoder->sampleRate) * 4;
encoder->audioBuffer = av_malloc(encoder->audioBufferSize);
_ffmpegOpenResampleContext(encoder);
av_frame_get_buffer(encoder->audioFrame, 0);
if (encoder->audio->codec->id == AV_CODEC_ID_AAC &&
@ -858,6 +853,11 @@ static void _ffmpegSetVideoDimensions(struct mAVStream* stream, unsigned width,
SWS_POINT, 0, 0, 0);
}
static void _ffmpegSetAudioRate(struct mAVStream* stream, unsigned rate) {
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
FFmpegEncoderSetInputSampleRate(encoder, rate);
}
void FFmpegEncoderSetInputFrameRate(struct FFmpegEncoder* encoder, int numerator, int denominator) {
reduceFraction(&numerator, &denominator);
encoder->frameCycles = numerator;
@ -866,3 +866,35 @@ void FFmpegEncoderSetInputFrameRate(struct FFmpegEncoder* encoder, int numerator
encoder->video->framerate = (AVRational) { denominator, numerator * encoder->frameskip };
}
}
void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder* encoder, int sampleRate) {
encoder->isampleRate = sampleRate;
if (encoder->resampleContext) {
av_freep(&encoder->audioBuffer);
#ifdef USE_LIBAVRESAMPLE
avresample_close(encoder->resampleContext);
#else
swr_free(&encoder->resampleContext);
#endif
_ffmpegOpenResampleContext(encoder);
}
}
void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder) {
encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 4, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate });
encoder->audioBuffer = av_malloc(encoder->audioBufferSize);
#ifdef USE_LIBAVRESAMPLE
encoder->resampleContext = avresample_alloc_context();
av_opt_set_int(encoder->resampleContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(encoder->resampleContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(encoder->resampleContext, "in_sample_rate", encoder->isampleRate, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_rate", encoder->sampleRate, 0);
av_opt_set_int(encoder->resampleContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0);
avresample_open(encoder->resampleContext);
#else
encoder->resampleContext = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, encoder->sampleFormat, encoder->sampleRate,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, encoder->isampleRate, 0, NULL);
swr_init(encoder->resampleContext);
#endif
}

View File

@ -56,6 +56,7 @@ struct FFmpegEncoder {
int height;
int iwidth;
int iheight;
int isampleRate;
int frameCycles;
int cycles;
int frameskip;
@ -78,6 +79,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder*, const char* vcodec, int vbr, i
bool FFmpegEncoderSetContainer(struct FFmpegEncoder*, const char* container);
void FFmpegEncoderSetDimensions(struct FFmpegEncoder*, int width, int height);
void FFmpegEncoderSetInputFrameRate(struct FFmpegEncoder*, int numerator, int denominator);
void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder*, int sampleRate);
void FFmpegEncoderSetLooping(struct FFmpegEncoder*, bool loop);
bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder*);
bool FFmpegEncoderOpen(struct FFmpegEncoder*, const char* outfile);

View File

@ -94,7 +94,7 @@ static void mGUIShowCheatSet(struct mGUIRunner* runner, struct mCheatDevice* dev
switch (action) {
case CHEAT_ADD_LINE:
strlcpy(keyboard.title, "Add line", sizeof(keyboard.title));
keyboard.maxLen = 12;
keyboard.maxLen = 17;
if (runner->params.getText(&keyboard) == GUI_KEYBOARD_DONE) {
mCheatAddLine(set, keyboard.result, 0);
_rebuildCheatView(&view.items, set);

View File

@ -24,6 +24,8 @@
const uint32_t DMG_SM83_FREQUENCY = 0x400000;
static const int CLOCKS_PER_BLIP_FRAME = 0x1000;
static const unsigned BLIP_BUFFER_SIZE = 0x4000;
static const int SAMPLE_INTERVAL = 32;
static const int FILTER = 65368;
const int GB_AUDIO_VOLUME_MAX = 0x100;
static bool _writeSweep(struct GBAudioSweep* sweep, uint8_t value);
@ -44,6 +46,8 @@ static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch);
static void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate);
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate);
static void GBAudioSample(struct GBAudio* audio, int32_t timestamp);
static const int _squareChannelDuty[4][8] = {
{ 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 1 },
@ -114,7 +118,9 @@ void GBAudioReset(struct GBAudio* audio) {
audio->ch3.wavedata8[15] = 0xFF;
audio->ch4 = (struct GBAudioNoiseChannel) { .envelope = { .dead = 2 } };
audio->frame = 0;
audio->sampleInterval = 128;
audio->sampleInterval = SAMPLE_INTERVAL * GB_MAX_SAMPLES;
audio->lastSample = 0;
audio->sampleIndex = 0;
audio->lastLeft = 0;
audio->lastRight = 0;
audio->capLeft = 0;
@ -371,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);
@ -468,6 +476,10 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
if (!audio->enable) {
return;
}
if (audio->p && channels != 0xF && timestamp - audio->lastSample > (int) (SAMPLE_INTERVAL * audio->timingFactor)) {
GBAudioSample(audio, timestamp);
}
if (audio->playingCh1 && (channels & 0x1)) {
int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor;
int32_t diff = timestamp - audio->ch1.lastUpdate;
@ -735,41 +747,64 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
*right = sampleRight * (1 + audio->volumeRight);
}
void GBAudioSample(struct GBAudio* audio, int32_t timestamp) {
int interval = SAMPLE_INTERVAL * audio->timingFactor;
timestamp -= audio->lastSample;
timestamp -= audio->sampleIndex * interval;
int sample;
for (sample = audio->sampleIndex; timestamp >= interval && sample < GB_MAX_SAMPLES; ++sample, timestamp -= interval) {
int16_t sampleLeft = 0;
int16_t sampleRight = 0;
GBAudioRun(audio, sample * interval + audio->lastSample, 0xF);
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
int16_t degradedLeft = sampleLeft - (audio->capLeft >> 16);
int16_t degradedRight = sampleRight - (audio->capRight >> 16);
audio->capLeft = (sampleLeft << 16) - degradedLeft * FILTER;
audio->capRight = (sampleRight << 16) - degradedRight * FILTER;
audio->currentSamples[sample].left = degradedLeft;
audio->currentSamples[sample].right = degradedRight;
}
audio->sampleIndex = sample;
if (sample == GB_MAX_SAMPLES) {
audio->lastSample += interval * GB_MAX_SAMPLES;
audio->sampleIndex = 0;
}
}
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
struct GBAudio* audio = user;
int16_t sampleLeft = 0;
int16_t sampleRight = 0;
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF);
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
GBAudioSample(audio, mTimingCurrentTime(audio->timing));
mCoreSyncLockAudio(audio->p->sync);
unsigned produced;
int16_t degradedLeft = sampleLeft - (audio->capLeft >> 16);
int16_t degradedRight = sampleRight - (audio->capRight >> 16);
audio->capLeft = (sampleLeft << 16) - degradedLeft * 65184;
audio->capRight = (sampleRight << 16) - degradedRight * 65184;
sampleLeft = degradedLeft;
sampleRight = degradedRight;
if ((size_t) blip_samples_avail(audio->left) < audio->samples) {
blip_add_delta(audio->left, audio->clock, sampleLeft - audio->lastLeft);
blip_add_delta(audio->right, audio->clock, sampleRight - audio->lastRight);
audio->lastLeft = sampleLeft;
audio->lastRight = sampleRight;
audio->clock += audio->sampleInterval;
if (audio->clock >= CLOCKS_PER_BLIP_FRAME) {
blip_end_frame(audio->left, CLOCKS_PER_BLIP_FRAME);
blip_end_frame(audio->right, CLOCKS_PER_BLIP_FRAME);
audio->clock -= CLOCKS_PER_BLIP_FRAME;
int i;
for (i = 0; i < GB_MAX_SAMPLES; ++i) {
int16_t sampleLeft = audio->currentSamples[i].left;
int16_t sampleRight = audio->currentSamples[i].right;
if ((size_t) blip_samples_avail(audio->left) < audio->samples) {
blip_add_delta(audio->left, audio->clock, sampleLeft - audio->lastLeft);
blip_add_delta(audio->right, audio->clock, sampleRight - audio->lastRight);
audio->lastLeft = sampleLeft;
audio->lastRight = sampleRight;
audio->clock += SAMPLE_INTERVAL;
if (audio->clock >= CLOCKS_PER_BLIP_FRAME) {
blip_end_frame(audio->left, CLOCKS_PER_BLIP_FRAME);
blip_end_frame(audio->right, CLOCKS_PER_BLIP_FRAME);
audio->clock -= CLOCKS_PER_BLIP_FRAME;
}
}
if (audio->p->stream && audio->p->stream->postAudioFrame) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
}
}
produced = blip_samples_avail(audio->left);
if (audio->p->stream && audio->p->stream->postAudioFrame) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
}
bool wait = produced >= audio->samples;
if (!mCoreSyncProduceAudio(audio->p->sync, audio->left, audio->samples)) {
// Interrupted
@ -1035,6 +1070,15 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt
void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state) {
GBAudioPSGSerialize(audio, &state->audio.psg, &state->audio.flags);
size_t i;
for (i = 0; i < GB_MAX_SAMPLES; ++i) {
STORE_16LE(audio->currentSamples[i].left, 0, &state->audio2.currentSamples[i].left);
STORE_16LE(audio->currentSamples[i].right, 0, &state->audio2.currentSamples[i].right);
}
STORE_32LE(audio->lastSample, 0, &state->audio2.lastSample);
state->audio2.sampleIndex = audio->sampleIndex;
STORE_32LE(audio->capLeft, 0, &state->audio.capLeft);
STORE_32LE(audio->capRight, 0, &state->audio.capRight);
STORE_32LE(audio->sampleEvent.when - mTimingCurrentTime(audio->timing), 0, &state->audio.nextSample);
@ -1044,6 +1088,15 @@ void GBAudioDeserialize(struct GBAudio* audio, const struct GBSerializedState* s
GBAudioPSGDeserialize(audio, &state->audio.psg, &state->audio.flags);
LOAD_32LE(audio->capLeft, 0, &state->audio.capLeft);
LOAD_32LE(audio->capRight, 0, &state->audio.capRight);
size_t i;
for (i = 0; i < GB_MAX_SAMPLES; ++i) {
LOAD_16LE(audio->currentSamples[i].left, 0, &state->audio2.currentSamples[i].left);
LOAD_16LE(audio->currentSamples[i].right, 0, &state->audio2.currentSamples[i].right);
}
LOAD_32LE(audio->lastSample, 0, &state->audio2.lastSample);
audio->sampleIndex = state->audio2.sampleIndex;
uint32_t when;
LOAD_32LE(when, 0, &state->audio.nextSample);
mTimingSchedule(audio->timing, &audio->sampleEvent, when);

View File

@ -427,6 +427,9 @@ static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
core->desiredVideoDimensions(core, &width, &height);
stream->videoDimensionsChanged(stream, width, height);
}
if (stream && stream->audioRateChanged) {
stream->audioRateChanged(stream, DMG_SM83_FREQUENCY / 32);
}
}
static bool _GBCoreLoadROM(struct mCore* core, struct VFile* vf) {
@ -657,6 +660,8 @@ static void _GBCoreReset(struct mCore* core) {
if (core->opts.skipBios) {
GBSkipBIOS(core->board);
}
mTimingInterrupt(&gb->timing);
}
static void _GBCoreRunFrame(struct mCore* core) {

View File

@ -33,6 +33,7 @@ static const uint8_t _registeredTrademark[] = {0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA
#define SGB_BIOS_CHECKSUM 0xEC8A83B9
#define SGB2_BIOS_CHECKSUM 0X53D0DD63
#define CGB_BIOS_CHECKSUM 0x41884E46
#define CGB0_BIOS_CHECKSUM 0xE8EF5318
#define AGB_BIOS_CHECKSUM 0xFFD6B0F1
mLOG_DEFINE_CATEGORY(GB, "GB", "gb");
@ -220,6 +221,8 @@ static void GBSramDeinit(struct GB* gb) {
GBMBCRTCWrite(gb);
} else if (gb->memory.mbcType == GB_HuC3) {
GBMBCHuC3Write(gb);
} else if (gb->memory.mbcType == GB_TAMA5) {
GBMBCTAMA5Write(gb);
}
}
gb->sramVf = NULL;
@ -244,6 +247,8 @@ bool GBLoadSave(struct GB* gb, struct VFile* vf) {
GBMBCRTCRead(gb);
} else if (gb->memory.mbcType == GB_HuC3) {
GBMBCHuC3Read(gb);
} else if (gb->memory.mbcType == GB_TAMA5) {
GBMBCTAMA5Read(gb);
}
}
return vf;
@ -271,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) {
@ -291,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) {
@ -329,6 +340,8 @@ void GBSramClean(struct GB* gb, uint32_t frameCount) {
GBMBCRTCWrite(gb);
} else if (gb->memory.mbcType == GB_HuC3) {
GBMBCHuC3Write(gb);
} else if (gb->memory.mbcType == GB_TAMA5) {
GBMBCTAMA5Write(gb);
}
if (gb->sramVf == gb->sramRealVf) {
if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) {
@ -507,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:
@ -838,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) {
@ -922,7 +957,8 @@ static void _enableInterrupts(struct mTiming* timing, void* user, uint32_t cycle
void GBHalt(struct SM83Core* cpu) {
struct GB* gb = (struct GB*) cpu->master;
if (!(gb->memory.ie & gb->memory.io[GB_REG_IF] & 0x1F)) {
cpu->cycles = cpu->nextEvent;
_GBAdvanceCycles(gb);
cpu->executionState = (cpu->executionState - 1) & 3;
cpu->halted = true;
} else if (!gb->memory.ime) {
mLOG(GB, GAME_ERROR, "HALT bug");

View File

@ -17,6 +17,12 @@ const uint32_t GB_LOGO_HASH = 0x46195417;
mLOG_DEFINE_CATEGORY(GB_MBC, "GB MBC", "gb.mbc");
static const uint8_t _tama6RTCMask[32] = {
//0 1 2 3 4 5 6 7 8 9 A B C D E F
0xF, 0x7, 0xF, 0x7, 0xF, 0x3, 0x7, 0xF, 0x3, 0xF, 0x1, 0xF, 0xF, 0x0, 0x0, 0x0,
0x0, 0x0, 0xF, 0x7, 0xF, 0x3, 0x7, 0xF, 0x3, 0x0, 0x1, 0x3, 0x0, 0x0, 0x0, 0x0,
};
static void _GBMBCNone(struct GB* gb, uint16_t address, uint8_t value) {
UNUSED(address);
UNUSED(value);
@ -453,10 +459,11 @@ void GBMBCInit(struct GB* gb) {
gb->memory.mbcRead = _GBHuC3Read;
break;
case GB_TAMA5:
mLOG(GB_MBC, WARN, "unimplemented MBC: TAMA5");
memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs));
gb->memory.mbcWrite = _GBTAMA5;
gb->memory.mbcRead = _GBTAMA5Read;
gb->memory.mbcState.tama5.rtcAlarmPage[GBTAMA6_RTC_PAGE] = 1;
gb->memory.mbcState.tama5.rtcFreePage0[GBTAMA6_RTC_PAGE] = 2;
gb->memory.mbcState.tama5.rtcFreePage1[GBTAMA6_RTC_PAGE] = 3;
gb->sramSize = 0x20;
break;
case GB_MBC3_RTC:
@ -540,6 +547,8 @@ void GBMBCInit(struct GB* gb) {
GBMBCRTCRead(gb);
} else if (gb->memory.mbcType == GB_HuC3) {
GBMBCHuC3Read(gb);
} else if (gb->memory.mbcType == GB_TAMA5) {
GBMBCTAMA5Read(gb);
}
}
@ -1514,6 +1523,174 @@ void _GBPocketCamCapture(struct GBMemory* memory) {
}
}
static const int _daysToMonth[] = {
[ 1] = 0,
[ 2] = 31,
[ 3] = 31 + 28,
[ 4] = 31 + 28 + 31,
[ 5] = 31 + 28 + 31 + 30,
[ 6] = 31 + 28 + 31 + 30 + 31,
[ 7] = 31 + 28 + 31 + 30 + 31 + 30,
[ 8] = 31 + 28 + 31 + 30 + 31 + 30 + 31,
[ 9] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
[10] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
[11] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
[12] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
};
static int _tama6DMYToDayOfYear(int day, int month, int year) {
if (month < 1 || month > 12) {
return -1;
}
day += _daysToMonth[month];
if (month > 2 && (year & 3) == 0) {
++day;
}
return day;
}
static int _tama6DayOfYearToMonth(int day, int year) {
int month;
for (month = 1; month < 12; ++month) {
if (day <= _daysToMonth[month + 1]) {
return month;
}
if (month == 2 && (year & 3) == 0) {
if (day == 60) {
return 2;
}
--day;
}
}
return 12;
}
static int _tama6DayOfYearToDayOfMonth(int day, int year) {
int month;
for (month = 1; month < 12; ++month) {
if (day <= _daysToMonth[month + 1]) {
return day - _daysToMonth[month];
}
if (month == 2 && (year & 3) == 0) {
if (day == 60) {
return 29;
}
--day;
}
}
return day - _daysToMonth[12];
}
static void _latchTAMA6Rtc(struct mRTCSource* rtc, struct GBTAMA5State* tama5, time_t* rtcLastLatch) {
time_t t;
if (rtc) {
if (rtc->sample) {
rtc->sample(rtc);
}
t = rtc->unixTime(rtc);
} else {
t = time(0);
}
time_t currentLatch = t;
t -= *rtcLastLatch;
*rtcLastLatch = currentLatch;
if (!t || tama5->disabled) {
return;
}
uint8_t* timerRegs = tama5->rtcTimerPage;
bool is24hour = tama5->rtcAlarmPage[GBTAMA6_RTC_PA1_24_HOUR];
int64_t diff;
diff = timerRegs[GBTAMA6_RTC_PA0_SECOND_1] + timerRegs[GBTAMA6_RTC_PA0_SECOND_10] * 10 + t % 60;
if (diff < 0) {
diff += 60;
t -= 60;
}
timerRegs[GBTAMA6_RTC_PA0_SECOND_1] = diff % 10;
timerRegs[GBTAMA6_RTC_PA0_SECOND_10] = (diff % 60) / 10;
t /= 60;
t += diff / 60;
diff = timerRegs[GBTAMA6_RTC_PA0_MINUTE_1] + timerRegs[GBTAMA6_RTC_PA0_MINUTE_10] * 10 + t % 60;
if (diff < 0) {
diff += 60;
t -= 60;
}
timerRegs[GBTAMA6_RTC_PA0_MINUTE_1] = diff % 10;
timerRegs[GBTAMA6_RTC_PA0_MINUTE_10] = (diff % 60) / 10;
t /= 60;
t += diff / 60;
diff = timerRegs[GBTAMA6_RTC_PA0_HOUR_1];
if (is24hour) {
diff += timerRegs[GBTAMA6_RTC_PA0_HOUR_10] * 10;
} else {
int hour10 = timerRegs[GBTAMA6_RTC_PA0_HOUR_10];
diff += (hour10 & 1) * 10;
diff += (hour10 & 2) * 12;
}
diff += t % 24;
if (diff < 0) {
diff += 24;
t -= 24;
}
if (is24hour) {
timerRegs[GBTAMA6_RTC_PA0_HOUR_1] = (diff % 24) % 10;
timerRegs[GBTAMA6_RTC_PA0_HOUR_10] = (diff % 24) / 10;
} else {
timerRegs[GBTAMA6_RTC_PA0_HOUR_1] = (diff % 12) % 10;
timerRegs[GBTAMA6_RTC_PA0_HOUR_10] = (diff % 12) / 10 + (diff / 12) * 2;
}
t /= 24;
t += diff / 24;
int day = timerRegs[GBTAMA6_RTC_PA0_DAY_1] + timerRegs[GBTAMA6_RTC_PA0_DAY_10] * 10;
int month = timerRegs[GBTAMA6_RTC_PA0_MONTH_1] + timerRegs[GBTAMA6_RTC_PA0_MONTH_10] * 10;
int year = timerRegs[GBTAMA6_RTC_PA0_YEAR_1] + timerRegs[GBTAMA6_RTC_PA0_YEAR_10] * 10;
int leapYear = tama5->rtcAlarmPage[GBTAMA6_RTC_PA1_LEAP_YEAR];
int dayOfWeek = timerRegs[GBTAMA6_RTC_PA0_WEEK];
int dayInYear = _tama6DMYToDayOfYear(day, month, leapYear);
diff = dayInYear + t;
while (diff <= 0) {
// Previous year
if (leapYear & 3) {
diff += 365;
} else {
diff += 366;
}
--year;
--leapYear;
}
while (diff > (leapYear & 3 ? 365 : 366)) {
// Future year
if (year % 4) {
diff -= 365;
} else {
diff -= 366;
}
++year;
++leapYear;
}
dayOfWeek = (dayOfWeek + diff) % 7;
year %= 100;
leapYear &= 3;
day = _tama6DayOfYearToDayOfMonth(diff, leapYear);
month = _tama6DayOfYearToMonth(diff, leapYear);
timerRegs[GBTAMA6_RTC_PA0_WEEK] = dayOfWeek;
tama5->rtcAlarmPage[GBTAMA6_RTC_PA1_LEAP_YEAR] = leapYear;
timerRegs[GBTAMA6_RTC_PA0_DAY_1] = day % 10;
timerRegs[GBTAMA6_RTC_PA0_DAY_10] = day / 10;
timerRegs[GBTAMA6_RTC_PA0_MONTH_1] = month % 10;
timerRegs[GBTAMA6_RTC_PA0_MONTH_10] = month / 10;
timerRegs[GBTAMA6_RTC_PA0_YEAR_1] = year % 10;
timerRegs[GBTAMA6_RTC_PA0_YEAR_10] = year / 10;
}
void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) {
struct GBMemory* memory = &gb->memory;
struct GBTAMA5State* tama5 = &memory->mbcState.tama5;
@ -1524,8 +1701,9 @@ void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) {
} else {
value &= 0xF;
if (tama5->reg < GBTAMA5_MAX) {
mLOG(GB_MBC, DEBUG, "TAMA5 write: %02X:%X", tama5->reg, value);
tama5->registers[tama5->reg] = value;
uint8_t address = ((tama5->registers[GBTAMA5_CS] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO];
uint8_t address = ((tama5->registers[GBTAMA5_ADDR_HI] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO];
uint8_t out = (tama5->registers[GBTAMA5_WRITE_HI] << 4) | tama5->registers[GBTAMA5_WRITE_LO];
switch (tama5->reg) {
case GBTAMA5_BANK_LO:
@ -1534,18 +1712,82 @@ void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) {
break;
case GBTAMA5_WRITE_LO:
case GBTAMA5_WRITE_HI:
case GBTAMA5_CS:
case GBTAMA5_ADDR_HI:
break;
case GBTAMA5_ADDR_LO:
switch (tama5->registers[GBTAMA5_CS] >> 1) {
switch (tama5->registers[GBTAMA5_ADDR_HI] >> 1) {
case 0x0: // RAM write
memory->sram[address] = out;
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
break;
case 0x1: // RAM read
break;
case 0x2: // Other commands
switch (address) {
case GBTAMA6_DISABLE_TIMER:
tama5->disabled = true;
tama5->rtcTimerPage[GBTAMA6_RTC_PAGE] &= 0x7;
tama5->rtcAlarmPage[GBTAMA6_RTC_PAGE] &= 0x7;
tama5->rtcFreePage0[GBTAMA6_RTC_PAGE] &= 0x7;
tama5->rtcFreePage1[GBTAMA6_RTC_PAGE] &= 0x7;
break;
case GBTAMA6_ENABLE_TIMER:
tama5->disabled = false;
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_SECOND_1] = 0;
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_SECOND_10] = 0;
tama5->rtcTimerPage[GBTAMA6_RTC_PAGE] |= 0x8;
tama5->rtcAlarmPage[GBTAMA6_RTC_PAGE] |= 0x8;
tama5->rtcFreePage0[GBTAMA6_RTC_PAGE] |= 0x8;
tama5->rtcFreePage1[GBTAMA6_RTC_PAGE] |= 0x8;
break;
case GBTAMA6_MINUTE_WRITE:
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_1] = out & 0xF;
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_10] = out >> 4;
break;
case GBTAMA6_HOUR_WRITE:
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_1] = out & 0xF;
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_10] = out >> 4;
break;
case GBTAMA6_DISABLE_ALARM:
tama5->rtcTimerPage[GBTAMA6_RTC_PAGE] &= 0xB;
tama5->rtcAlarmPage[GBTAMA6_RTC_PAGE] &= 0xB;
tama5->rtcFreePage0[GBTAMA6_RTC_PAGE] &= 0xB;
tama5->rtcFreePage1[GBTAMA6_RTC_PAGE] &= 0xB;
break;
case GBTAMA6_ENABLE_ALARM:
tama5->rtcTimerPage[GBTAMA6_RTC_PAGE] |= 0x4;
tama5->rtcAlarmPage[GBTAMA6_RTC_PAGE] |= 0x4;
tama5->rtcFreePage0[GBTAMA6_RTC_PAGE] |= 0x4;
tama5->rtcFreePage1[GBTAMA6_RTC_PAGE] |= 0x4;
break;
}
break;
case 0x4: // RTC access
address = tama5->registers[GBTAMA5_WRITE_LO];
if (address >= GBTAMA6_RTC_PAGE) {
break;
}
out = tama5->registers[GBTAMA5_WRITE_HI];
switch (tama5->registers[GBTAMA5_ADDR_LO]) {
case 0:
out &= _tama6RTCMask[address];
tama5->rtcTimerPage[address] = out;
break;
case 2:
out &= _tama6RTCMask[address | 0x10];
tama5->rtcAlarmPage[address] = out;
break;
case 4:
tama5->rtcFreePage0[address] = out;
break;
case 6:
tama5->rtcFreePage1[address] = out;
break;
}
break;
default:
mLOG(GB_MBC, STUB, "TAMA5 unknown address: %X-%02X:%02X", tama5->registers[GBTAMA5_CS] >> 1, address, out);
mLOG(GB_MBC, STUB, "TAMA5 unknown address: %02X:%02X", address, out);
break;
}
break;
default:
@ -1571,18 +1813,59 @@ uint8_t _GBTAMA5Read(struct GBMemory* memory, uint16_t address) {
return 0xFF;
} else {
uint8_t value = 0xF0;
uint8_t address = ((tama5->registers[GBTAMA5_CS] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO];
uint8_t address = ((tama5->registers[GBTAMA5_ADDR_HI] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO];
switch (tama5->reg) {
case GBTAMA5_ACTIVE:
return 0xF1;
case GBTAMA5_READ_LO:
case GBTAMA5_READ_HI:
switch (tama5->registers[GBTAMA5_CS] >> 1) {
case 1:
switch (tama5->registers[GBTAMA5_ADDR_HI] >> 1) {
case 0x1:
value = memory->sram[address];
break;
case 0x2:
mLOG(GB_MBC, STUB, "TAMA5 unknown read %s: %02X", tama5->reg == GBTAMA5_READ_HI ? "hi" : "lo", address);
_latchTAMA6Rtc(memory->rtc, tama5, &memory->rtcLastLatch);
switch (address) {
case GBTAMA6_MINUTE_READ:
value = (tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_10] << 4) | tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_1];
break;
case GBTAMA6_HOUR_READ:
value = (tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_10] << 4) | tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_1];
break;
default:
value = address;
break;
}
break;
case 0x4:
if (tama5->reg == GBTAMA5_READ_HI) {
mLOG(GB_MBC, GAME_ERROR, "TAMA5 reading RTC incorrectly");
break;
}
_latchTAMA6Rtc(memory->rtc, tama5, &memory->rtcLastLatch);
address = tama5->registers[GBTAMA5_WRITE_LO];
if (address > GBTAMA6_RTC_PAGE) {
value = 0;
break;
}
switch (tama5->registers[GBTAMA5_ADDR_LO]) {
case 1:
value = tama5->rtcTimerPage[address];
break;
case 3:
value = tama5->rtcTimerPage[address];
break;
case 5:
value = tama5->rtcTimerPage[address];
break;
case 7:
value = tama5->rtcTimerPage[address];
break;
}
break;
default:
mLOG(GB_MBC, STUB, "TAMA5 unknown read: %02X", tama5->reg);
mLOG(GB_MBC, STUB, "TAMA5 unknown read %s: %02X", tama5->reg == GBTAMA5_READ_HI ? "hi" : "lo", address);
break;
}
if (tama5->reg == GBTAMA5_READ_HI) {
@ -2015,3 +2298,62 @@ void GBMBCHuC3Write(struct GB* gb) {
_appendSaveSuffix(gb, &buffer, sizeof(buffer));
}
void GBMBCTAMA5Read(struct GB* gb) {
struct GBMBCTAMA5SaveBuffer buffer;
struct VFile* vf = gb->sramVf;
if (!vf) {
return;
}
vf->seek(vf, gb->sramSize, SEEK_SET);
if (vf->read(vf, &buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer)) {
gb->memory.mbcState.tama5.disabled = false;
return;
}
size_t i;
for (i = 0; i < 0x8; ++i) {
gb->memory.mbcState.tama5.rtcTimerPage[i * 2] = buffer.rtcTimerPage[i] & 0xF;
gb->memory.mbcState.tama5.rtcTimerPage[i * 2 + 1] = buffer.rtcTimerPage[i] >> 4;
gb->memory.mbcState.tama5.rtcAlarmPage[i * 2] = buffer.rtcAlarmPage[i] & 0xF;
gb->memory.mbcState.tama5.rtcAlarmPage[i * 2 + 1] = buffer.rtcAlarmPage[i] >> 4;
gb->memory.mbcState.tama5.rtcFreePage0[i * 2] = buffer.rtcFreePage0[i] & 0xF;
gb->memory.mbcState.tama5.rtcFreePage0[i * 2 + 1] = buffer.rtcFreePage0[i] >> 4;
gb->memory.mbcState.tama5.rtcFreePage1[i * 2] = buffer.rtcFreePage1[i] & 0xF;
gb->memory.mbcState.tama5.rtcFreePage1[i * 2 + 1] = buffer.rtcFreePage1[i] >> 4;
}
LOAD_64LE(gb->memory.rtcLastLatch, 0, &buffer.latchedUnix);
gb->memory.mbcState.tama5.disabled = !(gb->memory.mbcState.tama5.rtcTimerPage[GBTAMA6_RTC_PAGE] & 0x8);
gb->memory.mbcState.tama5.rtcTimerPage[GBTAMA6_RTC_PAGE] &= 0xC;
gb->memory.mbcState.tama5.rtcAlarmPage[GBTAMA6_RTC_PAGE] &= 0xC;
gb->memory.mbcState.tama5.rtcAlarmPage[GBTAMA6_RTC_PAGE] |= 1;
gb->memory.mbcState.tama5.rtcFreePage0[GBTAMA6_RTC_PAGE] &= 0xC;
gb->memory.mbcState.tama5.rtcFreePage0[GBTAMA6_RTC_PAGE] |= 2;
gb->memory.mbcState.tama5.rtcFreePage1[GBTAMA6_RTC_PAGE] &= 0xC;
gb->memory.mbcState.tama5.rtcFreePage1[GBTAMA6_RTC_PAGE] |= 3;
}
void GBMBCTAMA5Write(struct GB* gb) {
struct VFile* vf = gb->sramVf;
if (!vf) {
return;
}
struct GBMBCTAMA5SaveBuffer buffer = {0};
size_t i;
for (i = 0; i < 8; ++i) {
buffer.rtcTimerPage[i] = gb->memory.mbcState.tama5.rtcTimerPage[i * 2] & 0xF;
buffer.rtcTimerPage[i] |= gb->memory.mbcState.tama5.rtcTimerPage[i * 2 + 1] << 4;
buffer.rtcAlarmPage[i] = gb->memory.mbcState.tama5.rtcAlarmPage[i * 2] & 0xF;
buffer.rtcAlarmPage[i] |= gb->memory.mbcState.tama5.rtcAlarmPage[i * 2 + 1] << 4;
buffer.rtcFreePage0[i] = gb->memory.mbcState.tama5.rtcFreePage0[i * 2] & 0xF;
buffer.rtcFreePage0[i] |= gb->memory.mbcState.tama5.rtcFreePage0[i * 2 + 1] << 4;
buffer.rtcFreePage1[i] = gb->memory.mbcState.tama5.rtcFreePage1[i * 2] & 0xF;
buffer.rtcFreePage1[i] |= gb->memory.mbcState.tama5.rtcFreePage1[i * 2 + 1] << 4;
}
STORE_64LE(gb->memory.rtcLastLatch, 0, &buffer.latchedUnix);
_appendSaveSuffix(gb, &buffer, sizeof(buffer));
}

View File

@ -765,6 +765,24 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) {
STORE_16LE(memory->mbcState.mbc7.sr, 0, &state->memory.mbc7.sr);
STORE_32LE(memory->mbcState.mbc7.writable, 0, &state->memory.mbc7.writable);
break;
case GB_TAMA5:
STORE_64LE(memory->rtcLastLatch, 0, &state->memory.tama5.lastLatch);
state->memory.tama5.reg = memory->mbcState.tama5.reg;
for (i = 0; i < 4; ++i) {
state->tama5Registers.registers[i] = memory->mbcState.tama5.registers[i * 2] & 0xF;
state->tama5Registers.registers[i] |= memory->mbcState.tama5.registers[i * 2 + 1] << 4;
}
for (i = 0; i < 8; ++i) {
state->tama5Registers.rtcTimerPage[i] = memory->mbcState.tama5.rtcTimerPage[i * 2] & 0xF;
state->tama5Registers.rtcTimerPage[i] |= memory->mbcState.tama5.rtcTimerPage[i * 2 + 1] << 4;
state->tama5Registers.rtcAlarmPage[i] = memory->mbcState.tama5.rtcAlarmPage[i * 2] & 0xF;
state->tama5Registers.rtcAlarmPage[i] |= memory->mbcState.tama5.rtcAlarmPage[i * 2 + 1] << 4;
state->tama5Registers.rtcFreePage0[i] = memory->mbcState.tama5.rtcFreePage0[i * 2] & 0xF;
state->tama5Registers.rtcFreePage0[i] |= memory->mbcState.tama5.rtcFreePage0[i * 2 + 1] << 4;
state->tama5Registers.rtcFreePage1[i] = memory->mbcState.tama5.rtcFreePage1[i * 2] & 0xF;
state->tama5Registers.rtcFreePage1[i] |= memory->mbcState.tama5.rtcFreePage1[i * 2 + 1] << 4;
}
break;
case GB_HuC3:
STORE_64LE(memory->rtcLastLatch, 0, &state->memory.huc3.lastLatch);
state->memory.huc3.index = memory->mbcState.huc3.index;
@ -874,6 +892,24 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) {
LOAD_16LE(memory->mbcState.mbc7.sr, 0, &state->memory.mbc7.sr);
LOAD_32LE(memory->mbcState.mbc7.writable, 0, &state->memory.mbc7.writable);
break;
case GB_TAMA5:
LOAD_64LE(memory->rtcLastLatch, 0, &state->memory.tama5.lastLatch);
memory->mbcState.tama5.reg = state->memory.tama5.reg;
for (i = 0; i < 4; ++i) {
memory->mbcState.tama5.registers[i * 2] = state->tama5Registers.registers[i] & 0xF;
memory->mbcState.tama5.registers[i * 2 + 1] = state->tama5Registers.registers[i] >> 4;
}
for (i = 0; i < 8; ++i) {
memory->mbcState.tama5.rtcTimerPage[i * 2] = state->tama5Registers.rtcTimerPage[i] & 0xF;
memory->mbcState.tama5.rtcTimerPage[i * 2 + 1] = state->tama5Registers.rtcTimerPage[i] >> 4;
memory->mbcState.tama5.rtcAlarmPage[i * 2] = state->tama5Registers.rtcAlarmPage[i] & 0xF;
memory->mbcState.tama5.rtcAlarmPage[i * 2 + 1] = state->tama5Registers.rtcAlarmPage[i] >> 4;
memory->mbcState.tama5.rtcFreePage0[i * 2] = state->tama5Registers.rtcFreePage0[i] & 0xF;
memory->mbcState.tama5.rtcFreePage0[i * 2 + 1] = state->tama5Registers.rtcFreePage0[i] >> 4;
memory->mbcState.tama5.rtcFreePage1[i * 2] = state->tama5Registers.rtcFreePage1[i] & 0xF;
memory->mbcState.tama5.rtcFreePage1[i * 2 + 1] = state->tama5Registers.rtcFreePage1[i] >> 4;
}
break;
case GB_HuC3:
LOAD_64LE(memory->rtcLastLatch, 0, &state->memory.huc3.lastLatch);
memory->mbcState.huc3.index = state->memory.huc3.index;

View File

@ -627,7 +627,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i
if (startX == 0) {
_cleanOAM(softwareRenderer, y);
}
if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc) && !softwareRenderer->d.disableOBJ && (softwareRenderer->model != GB_MODEL_SCGB || softwareRenderer->sgbTransfer != 1)) {
if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc) && !softwareRenderer->d.disableOBJ) {
int i;
for (i = 0; i < softwareRenderer->objMax; ++i) {
GBVideoSoftwareRendererDrawObj(softwareRenderer, &softwareRenderer->obj[i], startX, endX, y);
@ -917,7 +917,7 @@ static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer
bgTile = ((int8_t*) maps)[topX + topY];
}
int p = highlight ? PAL_HIGHLIGHT_BG : PAL_BG;
if (renderer->model >= GB_MODEL_CGB && (!(renderer->model & GB_MODEL_SGB) || renderer->sgbTransfer != 1)) {
if (renderer->model >= GB_MODEL_CGB) {
GBObjAttributes attrs = attr[topX + topY];
p |= GBObjAttributesGetCGBPalette(attrs) * 4;
if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
@ -952,7 +952,7 @@ static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer
bgTile = ((int8_t*) maps)[topX + topY];
}
int p = highlight ? PAL_HIGHLIGHT_BG : PAL_BG;
if (renderer->model >= GB_MODEL_CGB && (!(renderer->model & GB_MODEL_SGB) || renderer->sgbTransfer != 1)) {
if (renderer->model >= GB_MODEL_CGB) {
GBObjAttributes attrs = attr[topX + topY];
p |= GBObjAttributesGetCGBPalette(attrs) * 4;
if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {

View File

@ -217,8 +217,7 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);
gb->timing.reroot = gb->timing.root;
gb->timing.root = NULL;
mTimingInterrupt(&gb->timing);
return true;
}

View File

@ -237,7 +237,11 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) {
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) {
audio->soundbias = value;
int32_t oldSampleInterval = audio->sampleInterval;
audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value);
if (oldSampleInterval != audio->sampleInterval && audio->p->stream && audio->p->stream->audioRateChanged) {
audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval);
}
}
void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) {
@ -401,20 +405,15 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing) - cyclesLate);
int samples = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
int sampleMask = 1 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
memset(audio->chA.samples, audio->chA.samples[samples - 1], sizeof(audio->chA.samples));
memset(audio->chB.samples, audio->chB.samples[samples - 1], sizeof(audio->chB.samples));
mCoreSyncLockAudio(audio->p->sync);
unsigned produced;
int32_t sampleSumLeft = 0;
int32_t sampleSumRight = 0;
int i;
for (i = 0; i < samples; ++i) {
int16_t sampleLeft = audio->currentSamples[i].left;
int16_t sampleRight = audio->currentSamples[i].right;
sampleSumLeft += sampleLeft;
sampleSumRight += sampleRight;
if ((size_t) blip_samples_avail(audio->psg.left) < audio->samples) {
blip_add_delta(audio->psg.left, audio->clock, sampleLeft - audio->lastLeft);
blip_add_delta(audio->psg.right, audio->clock, sampleRight - audio->lastRight);
@ -427,13 +426,9 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
audio->clock -= CLOCKS_PER_FRAME;
}
}
// TODO: Post all frames
if (audio->p->stream && audio->p->stream->postAudioFrame && (i & (sampleMask - 1)) == sampleMask - 1) {
sampleSumLeft /= sampleMask;
sampleSumRight /= sampleMask;
audio->p->stream->postAudioFrame(audio->p->stream, sampleSumLeft, sampleSumRight);
sampleSumLeft = 0;
sampleSumRight = 0;
if (audio->p->stream && audio->p->stream->postAudioFrame) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
}
}
produced = blip_samples_avail(audio->psg.left);

View File

@ -100,6 +100,9 @@ void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) {
hw->rtc.command = 0;
hw->rtc.control = 0x40;
memset(hw->rtc.time, 0, sizeof(hw->rtc.time));
hw->rtc.lastLatch = 0;
hw->rtc.offset = 0;
}
void _readPins(struct GBACartridgeHardware* hw) {
@ -278,6 +281,9 @@ void _rtcUpdateClock(struct GBACartridgeHardware* hw) {
} else {
t = time(0);
}
hw->rtc.lastLatch = t;
t -= hw->rtc.offset;
struct tm date;
localtime_r(&t, &date);
hw->rtc.time[0] = _rtcBCD(date.tm_year - 100);

View File

@ -503,6 +503,9 @@ static void _GBACoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
core->desiredVideoDimensions(core, &width, &height);
stream->videoDimensionsChanged(stream, width, height);
}
if (stream && stream->audioRateChanged) {
stream->audioRateChanged(stream, GBA_ARM7TDMI_FREQUENCY / gba->audio.sampleInterval);
}
}
static bool _GBACoreLoadROM(struct mCore* core, struct VFile* vf) {
@ -709,6 +712,8 @@ static void _GBACoreReset(struct mCore* core) {
if (forceSkip || (core->opts.skipBios && (gba->romVf || gba->memory.rom))) {
GBASkipBIOS(core->board);
}
mTimingInterrupt(&gba->timing);
}
static void _GBACoreRunFrame(struct mCore* core) {

View File

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

View File

@ -74,9 +74,9 @@ const uint8_t hleBios[SIZE_BIOS] = {
0xfa, 0x07, 0xa0, 0xe8, 0xfa, 0x07, 0xa0, 0xe8, 0x00, 0x10, 0xa0, 0xe3,
0xf0, 0x07, 0xbd, 0xe8, 0x1e, 0xff, 0x2f, 0xe1, 0xb0, 0x01, 0x00, 0x00,
0x04, 0xb0, 0x5b, 0xe2, 0xfd, 0xff, 0xff, 0x8a, 0x1e, 0xff, 0x2f, 0xe1,
0xc2, 0x03, 0xa0, 0xe3, 0x03, 0x10, 0x50, 0xe4, 0x00, 0x00, 0x51, 0xe3,
0x00, 0x10, 0xa0, 0x13, 0x10, 0xff, 0x2f, 0x11, 0x1c, 0x00, 0x9f, 0xe5,
0x00, 0x10, 0x90, 0xe5, 0x00, 0x00, 0x51, 0xe3, 0x00, 0x10, 0xa0, 0xe3,
0x10, 0xff, 0x2f, 0x11, 0xc0, 0x00, 0x40, 0xe2, 0x10, 0xff, 0x2f, 0xe1,
0xc2, 0xe3, 0xa0, 0xe3, 0x03, 0x10, 0x5e, 0xe4, 0x00, 0x00, 0x51, 0xe3,
0x00, 0x10, 0xa0, 0x13, 0x1e, 0xff, 0x2f, 0x11, 0x1c, 0xe0, 0x9f, 0xe5,
0x00, 0x10, 0x9e, 0xe5, 0x00, 0x00, 0x51, 0xe3, 0x00, 0x10, 0xa0, 0xe3,
0x1e, 0xff, 0x2f, 0x11, 0xc0, 0xe0, 0x4e, 0xe2, 0x1e, 0xff, 0x2f, 0xe1,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0xc0, 0x00, 0x00, 0x02
};

View File

@ -314,17 +314,17 @@ bhi StallCall
bx lr
resetBase:
mov r0, #0x8000003
ldrb r1, [r0], #-3
mov lr, #0x8000003
ldrb r1, [lr], #-3
cmp r1, #0
movne r1, #0
bxne r0
ldr r0, =0x20000C0
ldr r1, [r0]
bxne lr
ldr lr, =0x20000C0
ldr r1, [lr]
cmp r1, #0
mov r1, #0
bxne r0
sub r0, #0xC0
bx r0
bxne lr
sub lr, #0xC0
bx lr
.word 0
.word 0xE129F000

View File

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

View File

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

View File

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

View File

@ -211,8 +211,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
GBAMatrixDeserialize(gba, state);
}
gba->timing.reroot = gba->timing.root;
gba->timing.root = NULL;
mTimingInterrupt(&gba->timing);
return true;
}

View File

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

View File

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

View File

@ -132,9 +132,17 @@ static enum GUIKeyboardStatus _keyboardRun(struct GUIKeyboardParams* keyboard) {
utf16Buffer = params.inputTextBuffer;
utf8Buffer = keyboard->result;
i = keyboard->maxLen;
while (i > 0 && *utf16Buffer) {
uint32_t unichar = utf16Char((const uint16_t**) &utf16Buffer, &i);
utf8Buffer += toUtf8(unichar, utf8Buffer);
size_t bufferSize = sizeof(SceWChar16) * keyboard->maxLen;
while (bufferSize && *utf16Buffer) {
char buffer[4];
uint32_t unichar = utf16Char((const uint16_t**) &utf16Buffer, &bufferSize);
size_t bytes = toUtf8(unichar, buffer);
if (i < bytes) {
break;
}
memcpy(utf8Buffer, buffer, bytes);
utf8Buffer += bytes;
i -= bytes;
}
utf8Buffer[0] = 0;

View File

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

View File

@ -424,7 +424,7 @@ install(TARGETS ${BINARY_NAME}-qt
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt
BUNDLE DESTINATION ${APPDIR} COMPONENT ${BINARY_NAME}-qt)
if(UNIX AND NOT APPLE)
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop DESTINATION share/applications COMPONENT ${BINARY_NAME}-qt)
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop DESTINATION share/applications RENAME io.mgba.${PROJECT_NAME}.desktop COMPONENT ${BINARY_NAME}-qt)
endif()
if(UNIX AND NOT (APPLE AND DISTBUILD))
install(FILES ${CMAKE_SOURCE_DIR}/doc/mgba-qt.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-qt)

View File

@ -12,6 +12,9 @@
#include <QMenu>
#include <mgba/feature/commandline.h>
#ifdef M_CORE_GB
#include <mgba/internal/gb/overrides.h>
#endif
static const mOption s_frontendOptions[] = {
{ "ecard", true, '\0' },
@ -128,8 +131,10 @@ ConfigController::ConfigController(QObject* parent)
m_opts.interframeBlending = false;
mCoreConfigLoad(&m_config);
mCoreConfigLoadDefaults(&m_config, &m_opts);
#ifdef M_CORE_GB
mCoreConfigSetDefaultIntValue(&m_config, "sgb.borders", 1);
mCoreConfigSetDefaultIntValue(&m_config, "useCgbColors", 1);
mCoreConfigSetDefaultIntValue(&m_config, "gb.colors", GB_COLORS_CGB);
#endif
mCoreConfigMap(&m_config, &m_opts);
mSubParserGraphicsInit(&m_subparsers[0], &m_graphicsOpts);

View File

@ -613,6 +613,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()) {
@ -632,6 +633,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);
@ -660,6 +662,7 @@ void CoreController::loadState(QIODevice* iodev, int flags) {
if (flags != -1) {
m_loadStateFlags = flags;
}
mCoreThreadClearCrashed(&m_threadContext);
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
CoreController* controller = static_cast<CoreController*>(context->userData);
VFile* vf = controller->m_stateVf;

View File

@ -218,7 +218,7 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
CoreController::Interrupter interrupter(controller);
QMetaObject::invokeMethod(m_painter.get(), "start");
if (!m_gl) {
if (QGuiApplication::platformName() == "windows") {
if (shouldDisableUpdates()) {
setUpdatesEnabled(false);
}
} else {
@ -298,7 +298,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);
}
}
}
@ -306,7 +308,7 @@ void DisplayGL::unpauseDrawing() {
if (m_hasStarted) {
m_isDrawing = true;
QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
if (!m_gl && QGuiApplication::platformName() == "windows") {
if (!m_gl && shouldDisableUpdates()) {
setUpdatesEnabled(false);
}
}
@ -385,6 +387,16 @@ void DisplayGL::resizePainter() {
}
}
bool DisplayGL::shouldDisableUpdates() {
if (QGuiApplication::platformName() == "windows") {
return true;
}
if (QGuiApplication::platformName() == "xcb") {
return true;
}
return false;
}
void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
Display::setVideoProxy(proxy);
if (proxy) {

View File

@ -112,6 +112,7 @@ protected:
private:
void resizePainter();
bool shouldDisableUpdates();
static QHash<QSurfaceFormat, bool> s_supports;

View File

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

View File

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

View File

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

View File

@ -167,7 +167,7 @@ void VideoView::updatePresets() {
addPreset(m_ui.presetLossless, {
"MKV",
"libx264rgb",
"FLAC",
"WavPack",
-1,
0,
{ m_nativeWidth, m_nativeHeight }

View File

@ -324,6 +324,11 @@
<string>FLAC</string>
</property>
</item>
<item>
<property name="text">
<string>WavPack</string>
</property>
</item>
<item>
<property name="text">
<string>Opus</string>

View File

@ -50,7 +50,9 @@
#include "ReportView.h"
#include "ROMInfo.h"
#include "SaveConverter.h"
#ifdef ENABLE_SCRIPTING
#include "scripting/ScriptingView.h"
#endif
#include "SensorView.h"
#include "ShaderSelector.h"
#include "ShortcutController.h"
@ -141,10 +143,16 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi
});
#endif
#if defined(M_CORE_GBA)
resizeFrame(QSize(GBA_VIDEO_HORIZONTAL_PIXELS * i, GBA_VIDEO_VERTICAL_PIXELS * i));
QSize minimumSize = QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
#elif defined(M_CORE_GB)
resizeFrame(QSize(GB_VIDEO_HORIZONTAL_PIXELS * i, GB_VIDEO_VERTICAL_PIXELS * i));
QSize minimumSize = QSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
#endif
setMinimumSize(minimumSize);
if (i > 0) {
m_initialSize = minimumSize * i;
} else {
m_initialSize = minimumSize * 2;
}
setLogo();
connect(this, &Window::shutdown, m_logView, &QWidget::hide);
@ -199,7 +207,7 @@ void Window::argumentsPassed() {
}
#endif
if (m_config->graphicsOpts()->multiplier) {
if (m_config->graphicsOpts()->multiplier > 0) {
m_savedScale = m_config->graphicsOpts()->multiplier;
#if defined(M_CORE_GBA)
@ -207,7 +215,7 @@ void Window::argumentsPassed() {
#elif defined(M_CORE_GB)
QSize size(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
#endif
resizeFrame(size * m_savedScale);
m_initialSize = size * m_savedScale;
}
if (args->fname) {
@ -230,9 +238,8 @@ void Window::resizeFrame(const QSize& size) {
newSize.setHeight(geom.height());
}
}
m_screenWidget->setSizeHint(newSize);
newSize -= m_screenWidget->size();
newSize += this->size();
newSize -= centralWidget()->size();
if (!isFullScreen()) {
resize(newSize);
}
@ -262,7 +269,7 @@ void Window::loadConfig() {
reloadConfig();
if (opts->width && opts->height) {
resizeFrame(QSize(opts->width, opts->height));
m_initialSize = QSize(opts->width, opts->height);
}
if (opts->fullscreen) {
@ -698,9 +705,10 @@ void Window::keyReleaseEvent(QKeyEvent* event) {
}
void Window::resizeEvent(QResizeEvent*) {
QSize newSize = centralWidget()->size();
if (!isFullScreen()) {
m_config->setOption("height", m_screenWidget->height());
m_config->setOption("width", m_screenWidget->width());
m_config->setOption("height", newSize.height());
m_config->setOption("width", newSize.width());
}
int factor = 0;
@ -708,9 +716,9 @@ void Window::resizeEvent(QResizeEvent*) {
if (m_controller) {
size = m_controller->screenDimensions();
}
if (m_screenWidget->width() % size.width() == 0 && m_screenWidget->height() % size.height() == 0 &&
m_screenWidget->width() / size.width() == m_screenWidget->height() / size.height()) {
factor = m_screenWidget->width() / size.width();
if (newSize.width() % size.width() == 0 && newSize.height() % size.height() == 0 &&
newSize.width() / size.width() == newSize.height() / size.height()) {
factor = newSize.width() / size.width();
}
m_savedScale = factor;
for (QMap<int, Action*>::iterator iter = m_frameSizes.begin(); iter != m_frameSizes.end(); ++iter) {
@ -737,7 +745,9 @@ void Window::showEvent(QShowEvent* event) {
return;
}
m_wasOpened = true;
resizeFrame(m_screenWidget->sizeHint());
if (m_initialSize.isValid()) {
resizeFrame(m_initialSize);
}
QVariant windowPos = m_config->getQtOption("windowPos", m_playerId > 0 ? QString("player%0").arg(m_playerId) : QString());
bool maximized = m_config->getQtOption("maximized").toBool();
QRect geom = windowHandle()->screen()->availableGeometry();
@ -860,7 +870,7 @@ void Window::exitFullScreen() {
if (!isFullScreen()) {
return;
}
m_screenWidget->unsetCursor();
centralWidget()->unsetCursor();
menuBar()->show();
showNormal();
}
@ -908,7 +918,7 @@ void Window::gameStarted() {
}
m_focusCheck.start();
if (m_display->underMouse()) {
m_screenWidget->setCursor(Qt::BlankCursor);
centralWidget()->setCursor(Qt::BlankCursor);
}
CoreController::Interrupter interrupter(m_controller);
@ -1052,11 +1062,11 @@ void Window::reloadDisplayDriver() {
connect(m_display.get(), &QGBA::Display::hideCursor, [this]() {
if (centralWidget() == m_display.get()) {
m_screenWidget->setCursor(Qt::BlankCursor);
centralWidget()->setCursor(Qt::BlankCursor);
}
});
connect(m_display.get(), &QGBA::Display::showCursor, [this]() {
m_screenWidget->unsetCursor();
centralWidget()->unsetCursor();
});
m_display->configure(m_config);
@ -1858,7 +1868,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);
@ -2168,7 +2178,7 @@ void Window::updateMute() {
void Window::setLogo() {
m_screenWidget->setPixmap(m_logo);
m_screenWidget->setDimensions(m_logo.width(), m_logo.height());
m_screenWidget->unsetCursor();
centralWidget()->unsetCursor();
}
WindowBackground::WindowBackground(QWidget* parent)

View File

@ -195,6 +195,7 @@ private:
std::unique_ptr<AudioProcessor> m_audioProcessor;
std::unique_ptr<QGBA::Display> m_display;
QSize m_initialSize;
int m_savedScale;
// TODO: Move these to a new class

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More