/* -*- compile-command: "cd ../wasm && make MEMDEBUG=TRUE install -j3"; -*- */ /* * Copyright 2021 by Eric House (xwords@eehouse.org). All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include "strutils.h" #include "wasmdutil.h" #include "main.h" #include "dbgutil.h" #include "LocalizedStrIncludes.h" #include "wasmdict.h" typedef struct _WasmDUtilCtxt { XW_DUtilCtxt super; int dirtyCount; } WasmDUtilCtxt; EM_JS( void, fsSyncOut, (StringProc proc, void* closure), { FS.syncfs(false, function (err) { if ( err ) { console.log('sync done: ' + err); } if ( proc ) { let str = !err ? "success" : err.toString(); ccall('cbckString', null, ['number', 'number', 'string'], [proc, closure, str]); } }); }); static XP_U32 wasm_dutil_getCurSeconds( XW_DUtilCtxt* XP_UNUSED(duc), XWEnv XP_UNUSED(xwe) ) { return (XP_U32)time(NULL); } static const XP_UCHAR* wasm_dutil_getUserString( XW_DUtilCtxt* duc, XWEnv xwe, XP_U16 code ) { switch( code ) { case STRD_REMAINING_TILES_ADD: return (XP_UCHAR*)"+ %d [all remaining tiles]"; case STRD_UNUSED_TILES_SUB: return (XP_UCHAR*)"- %d [unused tiles]"; case STR_COMMIT_CONFIRM: return (XP_UCHAR*)"Are you sure you want to commit the current move?\n"; case STR_SUBMIT_CONFIRM: return (XP_UCHAR*)"Submit the current move?\n"; case STRD_TURN_SCORE: return (XP_UCHAR*)"Score for turn: %d\n"; case STR_BONUS_ALL: return (XP_UCHAR*)"Bonus for using all tiles: 50\n"; case STR_BONUS_ALL_SUB: return (XP_UCHAR*)"Bonus for using at least %d tiles: 50\n"; case STR_PENDING_PLAYER: return (XP_UCHAR*)"(remote)"; case STRD_TIME_PENALTY_SUB: return (XP_UCHAR*)" - %d [time]"; /* added.... */ case STRD_CUMULATIVE_SCORE: return (XP_UCHAR*)"Cumulative score: %d\n"; case STRS_TRAY_AT_START: return (XP_UCHAR*)"Tray at start: %s\n"; case STRS_MOVE_DOWN: return (XP_UCHAR*)"move (from %s down)\n"; case STRS_MOVE_ACROSS: return (XP_UCHAR*)"move (from %s across)\n"; case STRS_NEW_TILES: return (XP_UCHAR*)"New tiles: %s\n"; case STRSS_TRADED_FOR: return (XP_UCHAR*)"Traded %s for %s."; case STR_PASS: return (XP_UCHAR*)"pass\n"; case STR_PHONY_REJECTED: return (XP_UCHAR*)"Illegal word in move; turn lost!\n"; case STRD_ROBOT_TRADED: return (XP_UCHAR*)"%d tiles traded this turn."; case STR_ROBOT_MOVED: return (XP_UCHAR*)"The robot \"%s\" moved:\n"; case STRS_REMOTE_MOVED: return (XP_UCHAR*)"Remote player \"%s\" moved:\n"; #ifndef XWFEATURE_STANDALONE_ONLY case STR_LOCALPLAYERS: return (XP_UCHAR*)"Local players"; case STR_REMOTE: return (XP_UCHAR*)"Remote"; #endif case STR_TOTALPLAYERS: return (XP_UCHAR*)"Total players"; case STRS_VALUES_HEADER: return (XP_UCHAR*)"%s counts/values:\n"; case STRD_REMAINS_HEADER: return (XP_UCHAR*)"%d tiles left in pool."; case STRD_REMAINS_EXPL: return (XP_UCHAR*)"%d tiles left in pool and hidden trays:\n"; case STRSD_RESIGNED: return "[Resigned] %s: %d"; case STRSD_WINNER: return "[Winner] %s: %d"; case STRDSD_PLACER: return "[#%d] %s: %d"; case STR_DUP_MOVED: return (XP_UCHAR*)"Duplicate turn complete. Scores:\n"; case STR_DUP_CLIENT_SENT: return "This device has sent its moves to the host. When all players " "have sent their moves it will be your turn again."; case STRDD_DUP_HOST_RECEIVED: return "%d of %d players have reported their moves."; case STRD_DUP_TRADED: return "No moves made; traded %d tiles"; case STRSD_DUP_ONESCORE: return "%s: %d points\n"; default: XP_LOGF( "%s(code=%d)", __func__, code ); return (XP_UCHAR*)"unknown code"; } } static const XP_UCHAR* wasm_dutil_getUserQuantityString( XW_DUtilCtxt* duc, XWEnv xwe, XP_U16 code, XP_U16 quantity ) { return wasm_dutil_getUserString( duc, xwe, code ); } static void base16Encode( const uint8_t* data, int dataLen, char* out, int outLen ) { int used = 0; for ( int ii = 0; ii < dataLen; ++ii ) { uint8_t byt = data[ii]; out[used++] = 'A' + ((byt >> 4) & 0x0F); out[used++] = 'A' + (byt & 0x0F); } out[used] = '\0'; } static void base16Decode( uint8_t* decodeBuf, int len, const char* str ) { int offset = 0; for ( ; ; ) { char chr = *str++; if ( chr < 'A' || chr > ('A' + 16) ) { break; } uint8_t byt = (chr - 'A') << 4; chr = *str++; if ( chr < 'A' || chr > ('A' + 16) ) { break; } byt |= chr - 'A'; decodeBuf[offset++] = byt; } // XP_LOGFF( "offset: %d; len: %d", offset, len ); XP_ASSERT( offset == len ); } static void ensurePath(const char* keys[], char buf[], bool mkDirs) { buf[0] = '\0'; int offset = 0; offset += sprintf( buf+offset, "%s", ROOT_PATH ); /* Iterate path elements. Last is a file. So if we're not at the end, make a directory */ for ( int ii = 0; ; ++ii ) { const char* elem = keys[ii]; // XP_LOGFF( "elem[%d]: %s", ii, elem ); if ( !elem ) { break; } if ( mkDirs ) { struct stat statbuf; if ( 0 == stat(buf, &statbuf) ) { // XP_LOGFF( "%s already exists", buf ); } else { /* XP_LOGFF( "calling mkdir(%s) before appending %s", buf, elem ); */ int err = mkdir(buf, 0777); /* XP_LOGFF( "mkdir(%s) => %d", buf, err); */ if ( err != 0 ) { XP_LOGFF( "error from mkdir: %s", strerror(errno) ); } } } offset += sprintf( buf+offset, "/%s", elem ); // XP_LOGFF( "path now %s", buf ); } } static void wasm_dutil_storeStream( XW_DUtilCtxt* duc, XWEnv xwe, const XP_UCHAR* keys[], XWStreamCtxt* stream ) { char path[128]; ensurePath(keys, path, true); // XP_LOGFF( "opening %s", path ); int fd = open(path, O_RDWR | O_CREAT, 0666); if ( -1 == fd ) { XP_LOGFF( "error from open(%s): %s", path, strerror(errno)); } XP_ASSERT( fd != -1 ); /* firing */ const XP_U8* data = stream_getPtr( stream ); const XP_U16 len = stream_getSize( stream ); ssize_t nWritten = write( fd, data, len ); // XP_LOGFF( "wrote %d bytes to path %s", nWritten, path ); XP_ASSERT( nWritten == len ); ++((WasmDUtilCtxt*)duc)->dirtyCount; } static void wasm_dutil_loadStream( XW_DUtilCtxt* duc, XWEnv xwe, const XP_UCHAR* keys[], XWStreamCtxt* inOut ) { char path[128]; ensurePath(keys, path, false); // XP_LOGFF( "(path: %s [retry: %s])", path, path ); struct stat statbuf; int err = stat(path, &statbuf); if ( 0 == err ) { XP_U8 buf[statbuf.st_size]; int fd = open(path, O_RDONLY); ssize_t nRead = read(fd, buf, statbuf.st_size); close( fd ); stream_putBytes( inOut, buf, statbuf.st_size ); } else { XP_LOGFF( "no file at %s", path ); } } static void wasm_dutil_storePtr( XW_DUtilCtxt* duc, XWEnv xwe, const char* keys[], const void* data, XP_U32 len ) { char path[128]; ensurePath(keys, path, true); // XP_LOGFF( "opening %s", path ); int fd = open(path, O_RDWR | O_CREAT, 0666); if ( -1 == fd ) { XP_LOGFF( "error from open(%s): %s", path, strerror(errno)); } XP_ASSERT( fd != -1 ); /* firing */ ssize_t nWritten = write(fd, data, len); // XP_LOGFF( "wrote %d bytes to path %s", nWritten, path ); XP_ASSERT( nWritten == len ); ++((WasmDUtilCtxt*)duc)->dirtyCount; } static void wasm_dutil_loadPtr( XW_DUtilCtxt* duc, XWEnv xwe, const char* keys[], void* data, XP_U32* lenp ) { char path[128]; ensurePath(keys, path, false); // XP_LOGFF( "(path: %s)", path ); struct stat statbuf; int err = stat(path, &statbuf); if ( 0 == err ) { if ( NULL != data && statbuf.st_size <= *lenp ) { int fd = open(path, O_RDONLY); ssize_t nRead = read(fd, data, statbuf.st_size); // XP_LOGFF( "read %d bytes from file %s", nRead, path ); close( fd ); } *lenp = statbuf.st_size; } else { XP_LOGFF( "no file at %s", path ); *lenp = 0; /* does not exist */ } // XP_LOGFF( "(path: %s)=> (len: %d)", path, *lenp ); } /* Iterate over every child of the provided path. This isn't a recursive * operation since it's only at one level - UNLESS there are wildcards. * * We only call the callback when we've reached the end! */ static bool callWithKeys( XW_DUtilCtxt* duc, char path[], int depth, const char* keysIn[], const char* keysOut[], OnOneProc proc, void* closure ) { const char* elem = keysIn[depth]; // XP_LOGFF( "(path:%s; depth: %d; elem: %s)", path, depth, elem ); bool goOn = true; struct stat statbuf; int err = stat(path, &statbuf); if ( 0 == err ) { char* curBase = path + strlen(path); if ( NULL == elem ) { goOn = (*proc)(closure, keysOut); } else if ( 0 == strcmp( KEY_WILDCARD, elem ) ) { DIR* dir = opendir(path); if ( !!dir ) { struct dirent* dent; // for the directory entries while (goOn && (dent = readdir(dir)) != NULL) { if ( '.' != dent->d_name[0] ) { keysOut[depth] = dent->d_name; sprintf( curBase, "/%s", dent->d_name ); goOn = callWithKeys( duc, path, depth + 1, keysIn, keysOut, proc, closure ); } } closedir(dir); } else { XP_LOGFF( "error from opendir(%s): %s", path, strerror(errno)); } } else { keysOut[depth] = elem; sprintf( curBase, "/%s", elem ); goOn = callWithKeys( duc, path, depth + 1, keysIn, keysOut, proc, closure ); } keysOut[depth] = NULL; } return goOn; } # ifdef XWFEATURE_DEVICE static void wasm_dutil_forEach( XW_DUtilCtxt* duc, XWEnv xwe, const char* keysIn[], OnOneProc proc, void* closure ) { char path[256]; sprintf( path, "%s", ROOT_PATH ); const char* keysOut[10] = {0}; callWithKeys( duc, path, 0, keysIn, keysOut, proc, closure ); } static void deleteAll( char path[] ) { DIR* dir = opendir(path); if ( !!dir ) { char* base = path + strlen(path); struct dirent* dent; // for the directory entries while ((dent = readdir(dir)) != NULL) { if ( '.' != dent->d_name[0] ) { sprintf( base, "/%s", dent->d_name ); deleteAll( path ); } } closedir(dir); *base = '\0'; rmdir(path); XP_LOGFF( "removed directory %s", path ); } else { unlink( path ); XP_LOGFF( "removed file %s", path ); } } static void wasm_dutil_remove( XW_DUtilCtxt* duc, const XP_UCHAR* keys[] ) { char path[256]; ensurePath(keys, path, false); XP_LOGFF( "(path: %s)", path ); deleteAll(path); ++((WasmDUtilCtxt*)duc)->dirtyCount; } #endif #ifdef XWFEATURE_DEVID static const XP_UCHAR* wasm_dutil_getDevID( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe), DevIDType* typ ) { LOG_FUNC(); return NULL; } static void wasm_dutil_deviceRegistered( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe), DevIDType typ, const XP_UCHAR* idRelay ) { LOG_FUNC(); } #endif static XP_UCHAR* wasm_dutil_md5sum( XW_DUtilCtxt* duc, XWEnv xwe, const XP_U8* ptr, XP_U16 len ) { LOG_FUNC(); return NULL; } static void wasm_dutil_getUsername( XW_DUtilCtxt* duc, XWEnv xwe, XP_U16 num, XP_Bool isLocal, XP_Bool isRobot, XP_UCHAR* buf, XP_U16* len ) { LOG_FUNC(); if ( isRobot ) { *len = snprintf( buf, *len, "Robot %d", num ); } else if ( isLocal ) { Globals* globals = (Globals*)duc->closure; main_getLocalName( globals, buf, *len ); *len = strlen( buf ); } else { *len = snprintf( buf, *len, "Remote %d", num ); } } static void wasm_dutil_notifyPause( XW_DUtilCtxt* XP_UNUSED(duc), XWEnv XP_UNUSED(xwe), XP_U32 XP_UNUSED_DBG(gameID), DupPauseType XP_UNUSED_DBG(pauseTyp), XP_U16 XP_UNUSED_DBG(pauser), const XP_UCHAR* XP_UNUSED_DBG(name), const XP_UCHAR* XP_UNUSED_DBG(msg) ) { LOG_FUNC(); } static XP_Bool wasm_dutil_haveGame( XW_DUtilCtxt* duc, XWEnv xwe, XP_U32 gameID, XP_U8 channel ) { Globals* globals = (Globals*)duc->closure; return main_haveGame( globals, gameID, channel ); } static void wasm_dutil_onDupTimerChanged( XW_DUtilCtxt* XP_UNUSED(duc), XWEnv XP_UNUSED(xwe), XP_U32 XP_UNUSED_DBG(gameID), XP_U32 XP_UNUSED_DBG(oldVal), XP_U32 XP_UNUSED_DBG(newVal) ) { LOG_FUNC(); } static void wasm_dutil_onInviteReceived( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe), const NetLaunchInfo* nli ) { Globals* globals = (Globals*)duc->closure; main_gameFromInvite( globals, nli ); } static void wasm_dutil_onMessageReceived( XW_DUtilCtxt* duc, XWEnv xwe, XP_U32 gameID, const CommsAddrRec* from, const XP_U8* buf, XP_U16 len ) { Globals* globals = (Globals*)duc->closure; main_onGameMessage( globals, gameID, from, buf, len ); } static void wasm_dutil_onCtrlReceived( XW_DUtilCtxt* duc, XWEnv xwe, const XP_U8* buf, XP_U16 len ) { Globals* globals = (Globals*)duc->closure; main_onCtrlReceived( globals, buf, len ); } static void wasm_dutil_onGameGoneReceived( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe), XP_U32 gameID, const CommsAddrRec* from ) { Globals* globals = (Globals*)duc->closure; main_onGameGone( globals, gameID ); } static void wasm_dutil_ackMQTTMsg( XW_DUtilCtxt* duc, XWEnv xwe, XP_U32 gameID, const MQTTDevID* senderID, const XP_U8* msg, XP_U16 len ) { Globals* globals = (Globals*)duc->closure; main_ackMQTTMsg( globals, msg, len, gameID ); } static void onSynced(void* closure, const char* str) { XW_DUtilCtxt* duc = (XW_DUtilCtxt*)closure; XP_LOGFF( "(str: %s)", str ); if ( 0 == strcmp( "success", str ) ) { WasmDUtilCtxt* wduc = (WasmDUtilCtxt*)closure; wduc->dirtyCount = 0; } } void wasm_dutil_syncIf( XW_DUtilCtxt* duc ) { WasmDUtilCtxt* wduc = (WasmDUtilCtxt*)duc; if ( 0 < wduc->dirtyCount ) { wduc->dirtyCount = 0; StringProc proc = NULL; // onSynced; fsSyncOut(proc, duc); } } XW_DUtilCtxt* wasm_dutil_make( MPFORMAL VTableMgr* vtMgr, void* closure ) { WasmDUtilCtxt* result = XP_CALLOC( mpool, sizeof(*result) ); dutil_super_init( MPPARM(mpool) &result->super ); result->super.vtMgr = vtMgr; result->super.closure = closure; # define SET_PROC(nam) \ result->super.vtable.m_dutil_ ## nam = wasm_dutil_ ## nam; SET_PROC(getCurSeconds); SET_PROC(getUserString); SET_PROC(getUserQuantityString); SET_PROC(storeStream); SET_PROC(loadStream); SET_PROC(storePtr); SET_PROC(loadPtr); #ifdef XWFEATURE_DEVICE SET_PROC(forEach); SET_PROC(remove); #endif #ifdef XWFEATURE_SMS SET_PROC(phoneNumbersSame); #endif #ifdef XWFEATURE_DEVID SET_PROC(getDevID); SET_PROC(deviceRegistered); #endif #ifdef COMMS_CHECKSUM SET_PROC(md5sum); #endif SET_PROC(getUsername); SET_PROC(notifyPause); SET_PROC(haveGame); SET_PROC(onDupTimerChanged); SET_PROC(onInviteReceived); SET_PROC(onMessageReceived); SET_PROC(onCtrlReceived); SET_PROC(onGameGoneReceived); SET_PROC(ackMQTTMsg); # undef SET_PROC assertTableFull( &result->super.vtable, sizeof(result->super.vtable), "wasmdutil" ); LOG_RETURNF( "%p", &result->super ); return &result->super; } void wasm_dutil_destroy( XW_DUtilCtxt* dutil ) { XP_ASSERT(0); } void* wasm_dutil_mallocAndLoad( XW_DUtilCtxt* dutil, const XP_UCHAR* keys[], XP_U32* len ) { void* result = NULL; dutil_loadPtr( dutil, NULL, keys, NULL, len ); if ( 0 < *len ) { result = XP_MALLOC( dutil->mpool, *len ); dutil_loadPtr( dutil, NULL, keys, result, len ); } return result; }