]> git.rm.cloudns.org Git - xonotic/darkplaces.git/commitdiff
Merge PR 'Added Webassembly support and fixed GLES2 (somewhat)'
authorLockl00p <97256723+Lockl00p@users.noreply.github.com>
Sun, 11 Aug 2024 17:43:09 +0000 (12:43 -0500)
committerGitHub <noreply@github.com>
Sun, 11 Aug 2024 17:43:09 +0000 (03:43 +1000)
Webassembly version doesn’t work online and screen flashes on GLES2 are
opaque.

See https://github.com/DarkPlacesEngine/DarkPlaces/pull/169

---------

Signed-off-by: bones_was_here <bones_was_here@xonotic.au>
Co-authored-by: James O'Neill <hemebond@gmail.com>
Co-authored-by: bones_was_here <bones_was_here@xonotic.au>
21 files changed:
.github/workflows/build.yml
.gitignore
README.md
fs.c
gl_backend.c
host.c
host.h
makefile
makefile.inc
shader_glsl.h
sv_save.c
sys.h
sys_shared.c
sys_wasm.c [new file with mode: 0644]
vid_sdl.c
wasm/autoexec.cfg [new file with mode: 0644]
wasm/index.html [new file with mode: 0644]
wasm/pre.js [new file with mode: 0644]
wasm/preload/runhere [new file with mode: 0644]
wasm/standalone-shell.html [new file with mode: 0644]
wasm/standaloneprejs.js [new file with mode: 0644]

index 27fbd2d0c6e68e51e8c3c7a91bd9a5cc96dd9499..43d4c5a9ca5aeaa97144efb37932cbaadaa70681 100644 (file)
@@ -8,8 +8,7 @@ on:
   pull_request:
 
 jobs:
-  build:
-
+  sdl-release:
     runs-on: ubuntu-latest
     container:
       image: debian:latest
@@ -26,7 +25,7 @@ jobs:
           # make `git describe` show the correct commit hash
           fetch-depth: '0'
 
-      - name: Compile DP
+      - name: Compile
         run: |
           # prevent git complaining about dubious ownership of the repo
           chown -R root:root .
@@ -43,3 +42,56 @@ jobs:
           path: |
             darkplaces-sdl
 
+  wasm-release:
+    runs-on: ubuntu-latest
+    container:
+      image: debian:latest
+    steps:
+      - name: Install dependencies
+        run: |
+          apt update
+          apt install --yes build-essential python3-certifi
+
+      - name: Fetch repository
+        uses: actions/checkout@v4.1.1
+        with:
+          # make `git describe` show the correct commit hash
+          fetch-depth: '0'
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: '3'
+
+      - name: Install emsdk
+        uses: actions/checkout@v4.1.1
+        with:
+          repository: emscripten-core/emsdk
+          path: emsdk
+
+      - name: Compile
+        shell: bash
+        run: |
+          cd emsdk
+
+          # Download and install the latest SDK tools.
+          ./emsdk install latest
+
+          # Make the "latest" SDK "active" for the current user. (writes .emscripten file)
+          ./emsdk activate latest
+
+          # Activate PATH and other environment variables in the current terminal
+          source ./emsdk_env.sh
+
+          cd ..
+
+          # fail if there's any warnings
+          #export CC="cc"
+
+          make emscripten-release
+
+      - name: Upload WASM artifacts
+        uses: actions/upload-artifact@v4
+        with:
+          name: Wasm
+          path: |
+            darkplaces-wasm.js
index 4c4c53c9aedc82d5ecf50e147a54518cae8cee0f..1270a5524f77d2e9340c55ff339a0d153c4c507b 100644 (file)
@@ -17,6 +17,8 @@ ChangeLog
 darkplaces-agl
 darkplaces-glx
 darkplaces-sdl
+darkplaces-wasm.html
+darkplaces-wasm.js
 darkplaces-dedicated
 gmon.out
 *.ncb
@@ -41,3 +43,10 @@ Makefile.win
 *.pdb
 *.lib
 *.exp
+
+# emscripten
+build-obj/
+emsdk/
+docs/output/
+wasm/preload/*
+!wasm/preload/runhere
index df037d5c395d915012a76c835b768496c7d7751f..00f6f1fe5aa3d25773953209bd8b35a796dd5eff 100644 (file)
--- a/README.md
+++ b/README.md
@@ -109,6 +109,29 @@ The Release build crashes. The Debug x64 build doesn't crash (but is rather slow
 To get a build suitable for playing you'll need to use MinGW GCC, or download the autobuild from Xonotic (see above).
 
 
+### Web-Assembly (Emscripten)
+
+Note that this requires a linux device or WSL2.
+
+1. Install the [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html#installation-instructions-using-the-emsdk-recommended)
+1. After activating and sourcing emsdk, compile DarkPlaces for wasm using;
+   ```shell
+   make emscripten-release
+   ```
+1. Copy `darkplaces-wasm.js`, `wasm/index.html`, and `wasm/autoexec.cfg` files to your web server
+1. Copy the Quake `pak0.pak` and any other files into the same web server directory
+
+For the standalone version (single HTML file containing engine and data):
+1. Before compiling, copy game data and .cfg files to the appropriate gamedir in `wasm/preload` (for example, pak0 from Quake would be in `wasm/preload/id1/pak0.pak`)
+1. After activating and sourcing emsdk, compile DarkPlaces for wasm using;
+   ```shell
+   make emscripten-standalone
+   ```
+1. To start DP you must click somewhere in the window!
+1. If you want to upload files into the game filesystem, use `em_upload` in the darkplaces console (upload to /save if you want it to save across restarts)
+1. To save the stuff you uploaded to /save, use `em_save` (note that if you embedded the game, you won't be able to save changes to `/save/games`)
+
+
 ## Contributing
 
 [DarkPlaces Contributing Guidelines](CONTRIBUTING.md)
diff --git a/fs.c b/fs.c
index 5782896165ebb02c36739238157486350b6422e3..2ce128e86cf61fda5e11f477381bb469a4d26f34 100644 (file)
--- a/fs.c
+++ b/fs.c
@@ -2128,6 +2128,10 @@ void FS_Init_Commands(void)
        Cmd_AddCommand(CF_SHARED, "ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
        Cmd_AddCommand(CF_SHARED, "which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
 
+#ifdef __EMSCRIPTEN__
+       Sys_EM_Register_Commands();
+#endif
+
        if (com_startupgamegroup == GAME_NORMAL)
                Cmd_AddCommand(CF_SHARED, "game", FS_GameDir_f, "alias of gamedir, for compatibility with some Quake mod READMEs");
 }
index b5336a226223f62d852b5760a5b8e534117c8546..1dd6d742ebae8fad1c08741ee7c7fa950d091be1 100644 (file)
@@ -11,7 +11,11 @@ cvar_t gl_printcheckerror = {CF_CLIENT, "gl_printcheckerror", "0", "prints all O
 cvar_t r_render = {CF_CLIENT, "r_render", "1", "enables rendering 3D views (you want this on!)"};
 cvar_t r_renderview = {CF_CLIENT, "r_renderview", "1", "enables rendering 3D views (you want this on!)"};
 cvar_t r_waterwarp = {CF_CLIENT | CF_ARCHIVE, "r_waterwarp", "1", "warp view while underwater"};
+#ifdef USE_GLES2
+cvar_t gl_polyblend = {CF_CLIENT | CF_ARCHIVE, "gl_polyblend", "0", "tints view while underwater, hurt, etc"};
+#else
 cvar_t gl_polyblend = {CF_CLIENT | CF_ARCHIVE, "gl_polyblend", "1", "tints view while underwater, hurt, etc"};
+#endif
 
 cvar_t v_flipped = {CF_CLIENT, "v_flipped", "0", "mirror the screen (poor man's left handed mode)"};
 qbool v_flipped_state = false;
@@ -966,8 +970,12 @@ int R_Mesh_CreateFramebufferObject(rtexture_t *depthtexture, rtexture_t *colorte
        case RENDERPATH_GLES2:
                CHECKGLERROR
                qglGenFramebuffers(1, (GLuint*)&temp);CHECKGLERROR
-               R_Mesh_SetRenderTargets(temp, NULL, NULL, NULL, NULL, NULL);
+
+#ifndef USE_GLES2
+               R_Mesh_SetRenderTargets(temp, NULL, NULL, NULL, NULL, NULL);  // This breaks GLES2.
                // GL_ARB_framebuffer_object (GL3-class hardware) - depth stencil attachment
+#endif
+
 #ifdef USE_GLES2
                // FIXME: separate stencil attachment on GLES
                if (depthtexture  && depthtexture->texnum ) { qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT  , depthtexture->gltexturetypeenum , depthtexture->texnum , 0);CHECKGLERROR }
@@ -984,6 +992,7 @@ int R_Mesh_CreateFramebufferObject(rtexture_t *depthtexture, rtexture_t *colorte
                        if (depthtexture->glisdepthstencil) { qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT  , GL_RENDERBUFFER, depthtexture->renderbuffernum );CHECKGLERROR }
                }
 #endif
+
                if (colortexture  && colortexture->texnum ) { qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 , colortexture->gltexturetypeenum , colortexture->texnum , 0);CHECKGLERROR }
                if (colortexture2 && colortexture2->texnum) { qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 , colortexture2->gltexturetypeenum, colortexture2->texnum, 0);CHECKGLERROR }
                if (colortexture3 && colortexture3->texnum) { qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2 , colortexture3->gltexturetypeenum, colortexture3->texnum, 0);CHECKGLERROR }
diff --git a/host.c b/host.c
index 7853682274ca4090ec49a981e3ecdcee09afd1d6..ab0b077916725ed7c7157dbddb2ba711fcbb8f4d 100644 (file)
--- a/host.c
+++ b/host.c
@@ -211,7 +211,9 @@ void Host_SaveConfig(const char *file)
 
                Key_WriteBindings (f);
                Cvar_WriteVariables (&cvars_all, f);
-
+#ifdef __EMSCRIPTEN__
+               js_syncFS(false);
+#endif
                FS_Close (f);
        }
 }
@@ -371,7 +373,7 @@ void Host_UnlockSession(void)
 Host_Init
 ====================
 */
-static void Host_Init (void)
+void Host_Init (void)
 {
        int i;
        char vabuf[1024];
@@ -388,6 +390,9 @@ static void Host_Init (void)
 
        host.state = host_init;
 
+       host.realtime = 0;
+       host.dirtytime = Sys_DirtyTime();
+
        if (setjmp(host.abortframe)) // Huh?!
                Sys_Error("Engine initialization failed. Check the console (if available) for additional information.\n");
 
@@ -567,10 +572,10 @@ static void Host_Init (void)
 ===============
 Host_Shutdown
 
-Cleanly shuts down after the main loop exits.
+Cleanly shuts down, Host_Frame() must not be called again after this.
 ===============
 */
-static void Host_Shutdown(void)
+void Host_Shutdown(void)
 {
        if (Sys_CheckParm("-profilegameonly"))
                Sys_AllowProfiling(false);
@@ -617,7 +622,7 @@ Host_Frame
 Runs all active servers
 ==================
 */
-static double Host_Frame(double time)
+double Host_Frame(double time)
 {
        double cl_wait, sv_wait;
 
@@ -655,56 +660,3 @@ static double Host_Frame(double time)
        else
                return min(cl_wait, sv_wait); // listen server or singleplayer
 }
-
-// Cloudwalk: Most overpowered function declaration...
-static inline double Host_UpdateTime (double newtime, double oldtime)
-{
-       double time = newtime - oldtime;
-
-       if (time < 0)
-       {
-               // warn if it's significant
-               if (time < -0.01)
-                       Con_Printf(CON_WARN "Host_UpdateTime: time stepped backwards (went from %f to %f, difference %f)\n", oldtime, newtime, time);
-               time = 0;
-       }
-       else if (time >= 1800)
-       {
-               Con_Printf(CON_WARN "Host_UpdateTime: time stepped forward (went from %f to %f, difference %f)\n", oldtime, newtime, time);
-               time = 0;
-       }
-
-       return time;
-}
-
-void Host_Main(void)
-{
-       double time, oldtime, sleeptime;
-
-       Host_Init(); // Start!
-
-       host.realtime = 0;
-       oldtime = Sys_DirtyTime();
-
-       // Main event loop
-       while(host.state < host_shutdown) // see Sys_HandleCrash() comments
-       {
-               // Something bad happened, or the server disconnected
-               if (setjmp(host.abortframe))
-               {
-                       host.state = host_active; // In case we were loading
-                       continue;
-               }
-
-               host.dirtytime = Sys_DirtyTime();
-               host.realtime += time = Host_UpdateTime(host.dirtytime, oldtime);
-               oldtime = host.dirtytime;
-
-               sleeptime = Host_Frame(time);
-               ++host.framecount;
-               sleeptime -= Sys_DirtyTime() - host.dirtytime; // execution time
-               host.sleeptime = Sys_Sleep(sleeptime);
-       }
-
-       Host_Shutdown();
-}
diff --git a/host.h b/host.h
index d826c0b4d7534b61a97065f89ad19ac0fdd7224f..e72d75a2183426a5ecd69756bc1794f1b6c75d4f 100644 (file)
--- a/host.h
+++ b/host.h
@@ -54,13 +54,14 @@ typedef struct host_static_s
 } host_static_t;
 
 extern host_static_t host;
-
-void Host_Main(void);
 void Host_Error(const char *error, ...) DP_FUNC_PRINTF(1) DP_FUNC_NORETURN;
 void Host_UpdateVersion(void);
 void Host_LockSession(void);
 void Host_UnlockSession(void);
 void Host_AbortCurrentFrame(void) DP_FUNC_NORETURN;
 void Host_SaveConfig(const char *file);
+void Host_Init(void);
+double Host_Frame(double time);
+void Host_Shutdown(void);
 
 #endif
index b62194cd42dc9a93e07649c33aec7b33817f054b..923c8d8e11e698f560f887b9e17d81b324693dbc 100644 (file)
--- a/makefile
+++ b/makefile
@@ -110,6 +110,35 @@ ifeq ($(DP_MAKE_TARGET), linux)
        DP_LINK_XMP?=dlopen
 endif
 
+ifeq ($(DP_MAKE_TARGET), wasm)
+       MAKE=emmake make
+#      CFLAGS_EXTRA+=--use-port=sdl2 \
+#                    --use-port=libpng \
+#                    --use-port=libjpeg \
+#                    --use-port=zlib \
+#                    -DNOSUPPORTIPV6 \
+#                    -DUSE_GLES2
+       CFLAGS_EXTRA+=-s USE_SDL=2 \
+                     -s USE_LIBPNG=1 \
+                     -s USE_LIBJPEG=1 \
+                     -s USE_ZLIB=1 \
+                     -DNOSUPPORTIPV6 \
+                     -DUSE_GLES2
+
+       SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) $(SDLCONFIG_UNIXCFLAGS_X11)
+       SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) $(SDLCONFIG_UNIXLIBS_X11)
+       SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) $(SDLCONFIG_UNIXSTATICLIBS_X11)
+       DP_SSE=0
+
+       DP_LINK_SDL?=shared
+       DP_LINK_ZLIB?=shared
+       DP_LINK_JPEG?=dlopen
+       DP_LINK_ODE?=
+       DP_LINK_CRYPTO?=dlopen
+       DP_LINK_CRYPTO_RIJNDAEL?=dlopen
+       DP_LINK_XMP?=dlopen
+endif
+
 # Mac OS X configuration
 ifeq ($(DP_MAKE_TARGET), macosx)
        OBJ_ICON=
index 33cbc0438525ef568d77e9858a68673580f2f160..c0ec06fe3937a0626645c0fd348ba1f5f5b3ba53 100644 (file)
@@ -128,7 +128,7 @@ OBJ_MENU= \
 # built to give the executable a proper date string
 OBJ_SV= builddate.c sys_null.o vid_null.o thread_null.o $(OBJ_SND_NULL) $(OBJ_COMMON)
 OBJ_SDL= builddate.c sys_sdl.o vid_sdl.o thread_sdl.o $(OBJ_MENU) $(OBJ_SND_COMMON) $(OBJ_SND_XMP) snd_sdl.o $(OBJ_VIDEO_CAPTURE) $(OBJ_COMMON)
-
+OBJ_WASM= builddate.c sys_wasm.o vid_sdl.o thread_sdl.o $(OBJ_MENU) $(OBJ_SND_COMMON) $(OBJ_SND_XMP) snd_sdl.o $(OBJ_VIDEO_CAPTURE) $(OBJ_COMMON)
 
 # Compilation
 # -D_POSIX_C_SOURCE=200809L doesn't enable all of POSIX 2008, wtf?
@@ -212,6 +212,8 @@ EXE_UNIXSV=darkplaces-dedicated
 EXE_UNIXSDL=darkplaces-sdl
 EXE_UNIXSVNEXUIZ=nexuiz-dedicated
 EXE_UNIXSDLNEXUIZ=nexuiz-sdl
+EXE_WASMJS=darkplaces-wasm.js
+EXE_WASM=darkplaces-wasm.html
 
 CMD_UNIXRM=rm -rf
 CMD_UNIXCP=cp -f
@@ -227,6 +229,47 @@ LDFLAGS_LINUXSV=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -rdynamic
 LDFLAGS_LINUXSDL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -rdynamic $(LDFLAGS_UNIXSDL)
 
 
+##### WASM specific variables #####
+
+LDFLAGS_WASMJS=$(LDFLAGS_UNIXCOMMON) -s USE_SDL=2 \
+                                   -s USE_LIBPNG=1 \
+                                   -s USE_LIBJPEG=1 \
+                                   -s USE_ZLIB=1 \
+                                   -s INITIAL_MEMORY=128mb \
+                                   -s MAXIMUM_MEMORY=1gb \
+                                   -s SINGLE_FILE \
+                                   -s FULL_ES3 \
+                                   -s MIN_WEBGL_VERSION=2 \
+                                   -s MAX_WEBGL_VERSION=2 \
+                                   -s ALLOW_MEMORY_GROWTH=1 \
+                                   -s ASSERTIONS=2 \
+                                   -s TOTAL_STACK=32mb \
+                                   -DUSE_GLES2 \
+                                   -lidbfs.js \
+                                   --pre-js ../../../wasm/pre.js \
+                                   -s EXPORTED_RUNTIME_METHODS=callMain,addRunDependency,removeRunDependency
+
+LDFLAGS_WASM=$(LDFLAGS_UNIXCOMMON) -s USE_SDL=2 \
+                                   -s USE_LIBPNG=1 \
+                                   -s USE_LIBJPEG=1 \
+                                   -s USE_ZLIB=1 \
+                                   -s INITIAL_MEMORY=128mb \
+                                   -s MAXIMUM_MEMORY=1gb \
+                                   -s SINGLE_FILE \
+                                   -s FULL_ES3 \
+                                   -s MIN_WEBGL_VERSION=2 \
+                                   -s MAX_WEBGL_VERSION=2 \
+                                   -s ALLOW_MEMORY_GROWTH=1 \
+                                   -s ASSERTIONS=2 \
+                                   -s TOTAL_STACK=32mb \
+                                   -DUSE_GLES2 \
+                                   -lidbfs.js \
+                                   --pre-js ../../../wasm/standaloneprejs.js \
+                                   -s EXPORTED_RUNTIME_METHODS=callMain,addRunDependency,removeRunDependency \
+                                                                  --shell-file ../../../wasm/standalone-shell.html \
+                                                                  --embed-file ../../../wasm/preload@/preload
+
+
 ##### Mac OS X specific variables #####
 
 # Link
@@ -270,7 +313,7 @@ VPATH := ../../../
 .PHONY : clean clean-profile help \
         debug profile release \
         sv-debug sv-profile sv-release \
-        sdl-debug sdl-profile sdl-release
+        sdl-debug sdl-profile sdl-release emscripten-release
 
 help:
        @echo
@@ -301,6 +344,9 @@ help:
        @echo "* $(MAKE) sdl-release          : make SDL client (release version)"
        @echo "* $(MAKE) sdl-nexuiz           : make SDL client with nexuiz icon (release version)"
        @echo
+       @echo "* $(MAKE) emscripten-release   : make WASM client (release version)"
+       @echo "* $(MAKE) emscripten-standalone: make standalone WASM client (release version)"
+       @echo
 
 debug :
        $(MAKE) $(TARGETS_DEBUG)
@@ -357,6 +403,24 @@ sdl-release :
                DP_MAKE_TARGET=$(DP_MAKE_TARGET) \
                EXE='$(EXE_SDL)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' CFLAGS_SDL='$(SDLCONFIG_CFLAGS)' LDFLAGS_COMMON='$(LDFLAGS_SDL)' LEVEL=1
 
+emscripten-release :
+       $(MAKE) wasm-release \
+               DP_MAKE_TARGET="wasm" \
+               EXE='$(EXE_WASMJS)' \
+               CFLAGS_FEATURES='$(CFLAGS_CLIENT)' \
+               CFLAGS_SDL='$(SDLCONFIG_CFLAGS)' \
+               LDFLAGS_COMMON='$(LDFLAGS_WASMJS)' \
+               LEVEL=1
+
+emscripten-standalone :
+       $(MAKE) wasm-release \
+               DP_MAKE_TARGET="wasm" \
+               EXE='$(EXE_WASM)' \
+               CFLAGS_FEATURES='$(CFLAGS_CLIENT) -DWASM_USER_ADJUSTABLE' \
+               CFLAGS_SDL='$(SDLCONFIG_CFLAGS)' \
+               LDFLAGS_COMMON='$(LDFLAGS_WASM)' \
+               LEVEL=1
+
 sdl-release-profile :
        $(MAKE) bin-release-profile \
                DP_MAKE_TARGET=$(DP_MAKE_TARGET) \
@@ -409,6 +473,16 @@ bin-release-profile :
                LDFLAGS='$(LDFLAGS_RELEASE) $(LDFLAGS_COMMON)' LEVEL=2
        $(STRIP) $(EXE)
 
+wasm-release :
+       $(CHECKLEVEL1)
+       @echo
+       @echo '========== $(EXE) (release) =========='
+       $(MAKE) prepare BUILD_DIR=build-obj/release/$(EXE)
+       $(MAKE) -C build-obj/release/$(EXE) $(EXE) \
+               DP_MAKE_TARGET=$(DP_MAKE_TARGET) \
+               CFLAGS='$(CFLAGS_COMMON) $(CFLAGS_FEATURES) $(CFLAGS_EXTRA) $(CFLAGS_RELEASE) $(OPTIM_RELEASE)'\
+               LDFLAGS='$(LDFLAGS_RELEASE) $(LDFLAGS_COMMON)' LEVEL=2
+
 prepare :
        $(CMD_MKDIR) $(BUILD_DIR)
        $(CMD_CP) makefile.inc $(BUILD_DIR)/
@@ -483,6 +557,15 @@ $(EXE_SDL): $(OBJ_SDL) $(OBJ_ICON)
        $(CHECKLEVEL2)
        $(DO_LD)
 
+$(EXE_WASM): $(OBJ_WASM) $(OBJ_ICON)
+       $(CHECKLEVEL2)
+       $(DO_LD)
+
+$(EXE_WASMJS): $(OBJ_WASM) $(OBJ_ICON)
+       $(CHECKLEVEL2)
+       $(DO_LD)
+
+
 $(EXE_SVNEXUIZ): $(OBJ_SV) $(OBJ_ICON_NEXUIZ)
        $(CHECKLEVEL2)
        $(DO_LD)
@@ -497,6 +580,8 @@ $(EXE_SDLNEXUIZ): $(OBJ_SDL) $(OBJ_ICON_NEXUIZ)
 clean:
        -$(CMD_RM) $(EXE_SV)
        -$(CMD_RM) $(EXE_SDL)
+       -$(CMD_RM) $(EXE_WASM)
+       -$(CMD_RM) $(EXE_WASMJS)
        -$(CMD_RM) $(EXE_SVNEXUIZ)
        -$(CMD_RM) $(EXE_SDLNEXUIZ)
        -$(CMD_RM) *.o
index 5a4b224a2e88b31b1363444828ed5c824cb78317..db72342bb6c31735bde60a0f29e65335d40a887b 100644 (file)
@@ -41,6 +41,9 @@
 "\n",
 "invariant gl_Position; // fix for lighting polygons not matching base surface\n",
 "# endif\n",
+#ifdef USE_GLES2
+"precision highp float;\n",
+#endif
 "#if defined(GLSL130) || defined(GLSL140)\n",
 "precision highp float;\n",
 "# ifdef VERTEX_SHADER\n",
index c2c7442bbd6d0f1bc4b77549cedcaab50507ba87..5901b6dc66de7f1c08a8b0554c9ac0a585b169f1 100644 (file)
--- a/sv_save.c
+++ b/sv_save.c
@@ -20,6 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 #include "quakedef.h"
 #include "prvm_cmds.h"
+#include "sys.h"
 
 /*
 ===============================================================================
@@ -173,6 +174,9 @@ void SV_Savegame_to(prvm_prog_t *prog, const char *name)
 #endif
 
        FS_Close (f);
+#ifdef __EMSCRIPTEN__
+       js_syncFS(false);
+#endif
        Con_Print("done.\n");
 }
 
diff --git a/sys.h b/sys.h
index a5c6b275b2e08d9d2c5e2d45251d4c8a1e3d5e11..c2f238e21973677622703296beaf71d0e239533f 100644 (file)
--- a/sys.h
+++ b/sys.h
@@ -42,6 +42,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #endif
 # define DP_MOBILETOUCH        1
 # define DP_FREETYPE_STATIC 1
+#elif defined(__EMSCRIPTEN__) //this also defines linux, so it must come first
+# define DP_OS_NAME            "Browser"
+# define DP_OS_STR             "browser"
+# define DP_ARCH_STR   "WASM-32"
 #elif defined(__linux__)
 # define DP_OS_NAME            "Linux"
 # define DP_OS_STR             "linux"
@@ -252,6 +256,11 @@ void Sys_SDL_HandleEvents(void);
 
 char *Sys_SDL_GetClipboardData (void);
 
+#ifdef __EMSCRIPTEN__ //WASM-specific functions
+bool js_syncFS (bool x);
+void Sys_EM_Register_Commands(void);
+#endif
+
 extern qbool sys_supportsdlgetticks;
 unsigned int Sys_SDL_GetTicks (void); // wrapper to call SDL_GetTicks
 void Sys_SDL_Delay (unsigned int milliseconds); // wrapper to call SDL_Delay
index b955e8278aedfd5b2ef42c3c3da05be618d28099..d460b77b66b50fbe1dacb809a4073f274469c01c 100644 (file)
@@ -1102,6 +1102,73 @@ static void Sys_InitSignals(void)
 #endif
 }
 
+// Cloudwalk: Most overpowered function declaration...
+static inline double Sys_UpdateTime (double newtime, double oldtime)
+{
+       double time = newtime - oldtime;
+
+       if (time < 0)
+       {
+               // warn if it's significant
+               if (time < -0.01)
+                       Con_Printf(CON_WARN "Host_UpdateTime: time stepped backwards (went from %f to %f, difference %f)\n", oldtime, newtime, time);
+               time = 0;
+       }
+       else if (time >= 1800)
+       {
+               Con_Printf(CON_WARN "Host_UpdateTime: time stepped forward (went from %f to %f, difference %f)\n", oldtime, newtime, time);
+               time = 0;
+       }
+
+       return time;
+}
+
+#ifdef __EMSCRIPTEN__
+       #include <emscripten.h>
+#endif
+/// JS+WebGL doesn't support a main loop, only a function called to run a frame.
+static void Sys_Frame(void)
+{
+       double time, newtime, sleeptime;
+#ifdef __EMSCRIPTEN__
+       static double sleepstarttime = 0;
+       host.sleeptime = Sys_DirtyTime() - sleepstarttime;
+#endif
+
+       if (setjmp(host.abortframe)) // Something bad happened, or the server disconnected
+               host.state = host_active; // In case we were loading
+
+       if (host.state >= host_shutdown) // see Sys_HandleCrash() comments
+       {
+#ifdef __EMSCRIPTEN__
+               emscripten_cancel_main_loop();
+#endif
+#ifdef __ANDROID__
+               Sys_AllowProfiling(false);
+#endif
+               Host_Shutdown();
+               exit(0);
+       }
+
+       newtime = Sys_DirtyTime();
+       host.realtime += time = Sys_UpdateTime(newtime, host.dirtytime);
+       host.dirtytime = newtime;
+
+       sleeptime = Host_Frame(time);
+       ++host.framecount;
+       sleeptime -= Sys_DirtyTime() - host.dirtytime; // execution time
+
+#ifdef __EMSCRIPTEN__
+       // This platform doesn't support a main loop... it will sleep when Sys_Frame() returns.
+       // Not using emscripten_sleep() via Sys_Sleep() because it would cause two sleeps per frame.
+       if (!vid_vsync.integer) // see VID_SetVsync_c()
+               emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, host.restless ? 0 : sleeptime * 1000.0);
+       sleepstarttime = Sys_DirtyTime();
+#else
+       host.sleeptime = Sys_Sleep(sleeptime);
+#endif
+}
+
 /** main() but renamed so we can wrap it in sys_sdl.c and sys_null.c
  * to avoid needing to include SDL.h in this file (would make the dedicated server require SDL).
  * SDL builds need SDL.h in the file where main() is defined because SDL renames and wraps main().
@@ -1141,18 +1208,13 @@ int Sys_Main(int argc, char *argv[])
        Sys_SetTimerResolution();
 #endif
 
-       Host_Main();
-
-#ifdef __ANDROID__
-       Sys_AllowProfiling(false);
-#endif
-
-#ifndef WIN32
-       fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) & ~O_NONBLOCK);
-       fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) & ~O_NONBLOCK);
+       Host_Init();
+#ifdef __EMSCRIPTEN__
+       emscripten_set_main_loop(Sys_Frame, 0, true); // doesn't return
+#else
+       while(true)
+               Sys_Frame();
 #endif
-       fflush(stdout);
-       fflush(stderr);
 
        return 0;
 }
diff --git a/sys_wasm.c b/sys_wasm.c
new file mode 100644 (file)
index 0000000..167cea5
--- /dev/null
@@ -0,0 +1,307 @@
+/*
+ * Include this BEFORE darkplaces.h because it breaks wrapping
+ * _Static_assert. Cloudwalk has no idea how or why so don't ask.
+ */
+#include <SDL.h>
+
+#include "darkplaces.h"
+#include "fs.h"
+#include "vid.h"
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#include <string.h>
+
+
+EM_JS(float, js_GetViewportWidth, (void), {
+       return document.documentElement.clientWidth
+});
+EM_JS(float, js_GetViewportHeight, (void), {
+       return document.documentElement.clientHeight
+});
+static EM_BOOL em_on_resize(int etype, const EmscriptenUiEvent *event, void *UData)
+{
+       if(vid_resizable.integer)
+       {
+               Cvar_SetValueQuick(&vid_width, js_GetViewportWidth());
+               Cvar_SetValueQuick(&vid_height, js_GetViewportHeight());
+               Cvar_SetQuick(&vid_fullscreen, "0");
+       }
+       return EM_FALSE;
+}
+
+
+// =======================================================================
+// General routines
+// =======================================================================
+
+#ifdef WASM_USER_ADJUSTABLE
+EM_JS(char *, js_listfiles, (const char *directory), {
+       if(UTF8ToString(directory) == "")
+       {
+               console.log("listing cwd");
+               return stringToNewUTF8(FS.readdir(FS.cwd()).toString());
+       }
+
+       try
+       {
+               return stringToNewUTF8(FS.readdir(UTF8ToString(directory)).toString());
+       }
+       catch (error)
+       {
+               return stringToNewUTF8("directory not found");
+       }
+});
+static void em_listfiles_f(cmd_state_t *cmd)
+{
+       char *output = js_listfiles(Cmd_Argc(cmd) == 2 ? Cmd_Argv(cmd, 1) : "");
+
+       Con_Printf("%s\n", output);
+       free(output);
+}
+
+EM_JS(char *, js_upload, (const char *todirectory), {
+       if (UTF8ToString(todirectory).slice(-1) != "/")
+       {
+               currentname = UTF8ToString(todirectory) + "/";
+       }
+       else
+       {
+               currentname = UTF8ToString(todirectory);
+       }
+
+       file_selector.click();
+       return stringToNewUTF8("Upload started");
+});
+static void em_upload_f(cmd_state_t *cmd)
+{
+       char *output = js_upload(Cmd_Argc(cmd) == 2 ? Cmd_Argv(cmd, 1) : fs_basedir);
+
+       Con_Printf("%s\n", output);
+       free(output);
+}
+
+EM_JS(char *, js_rm, (const char *path), {
+       const mode = FS.lookupPath(UTF8ToString(path)).node.mode;
+
+       if (FS.isFile(mode))
+       {
+               FS.unlink(UTF8ToString(path));
+               return stringToNewUTF8("File removed");
+       }
+
+       return stringToNewUTF8(UTF8ToString(path)+" is not a File.");
+});
+static void em_rm_f(cmd_state_t *cmd)
+{
+       if (Cmd_Argc(cmd) != 2)
+               Con_Printf("No file to remove\n");
+       else
+       {
+               char *output = js_rm(Cmd_Argv(cmd, 1));
+               Con_Printf("%s\n", output);
+               free(output);
+       }
+}
+
+EM_JS(char *, js_rmdir, (const char *path), {
+       const mode = FS.lookupPath(UTF8ToString(path)).node.mode;
+       if (FS.isDir(mode))
+       {
+               try
+               {
+                       FS.rmdir(UTF8ToString(path));
+               }
+               catch (error)
+               {
+                       return stringToNewUTF8("Unable to remove directory. Is it not empty?");
+               }
+               return stringToNewUTF8("Directory removed");
+       }
+
+       return stringToNewUTF8(UTF8ToString(path)+" is not a directory.");
+});
+static void em_rmdir_f(cmd_state_t *cmd)
+{
+       if (Cmd_Argc(cmd) != 2)
+               Con_Printf("No directory to remove\n");
+       else
+       {
+               char *output = js_rmdir(Cmd_Argv(cmd, 1));
+               Con_Printf("%s\n", output);
+               free(output);
+       }
+}
+
+EM_JS(char *, js_mkd, (const char *path), {
+       try
+       {
+               FS.mkdir(UTF8ToString(path));
+       }
+       catch (error)
+       {
+               return stringToNewUTF8("Unable to create directory. Does it already exist?");
+       }
+       return stringToNewUTF8(UTF8ToString(path)+" directory was created.");
+});
+static void em_mkdir_f(cmd_state_t *cmd)
+{
+       if (Cmd_Argc(cmd) != 2)
+               Con_Printf("No directory to create\n");
+       else
+       {
+               char *output = js_mkd(Cmd_Argv(cmd, 1));
+               Con_Printf("%s\n", output);
+               free(output);
+       }
+}
+
+EM_JS(char *, js_move, (const char *oldpath, const char *newpath), {
+       try
+       {
+               FS.rename(UTF8ToString(oldpath),UTF8ToString(newpath))
+       }
+       catch (error)
+       {
+               return stringToNewUTF8("unable to move.");
+       }
+       return stringToNewUTF8("File Moved");
+});
+static void em_mv_f(cmd_state_t *cmd)
+{
+       if (Cmd_Argc(cmd) != 3)
+               Con_Printf("Nothing to move\n");
+       else
+       {
+               char *output = js_move(Cmd_Argv(cmd,1), Cmd_Argv(cmd,2));
+               Con_Printf("%s\n", output);
+               free(output);
+       }
+}
+
+static void em_wss_f(cmd_state_t *cmd)
+{
+       if (Cmd_Argc(cmd) != 3)
+               Con_Printf("Not Enough Arguments (Expected URL and subprotocol)\n");
+       else
+       {
+               if(strcmp(Cmd_Argv(cmd,2),"binary") == 0 || strcmp(Cmd_Argv(cmd,2),"text") == 0)
+                       Con_Printf("Set Websocket URL to %s and subprotocol to %s.\n", Cmd_Argv(cmd,1), Cmd_Argv(cmd,2));
+               else
+                       Con_Printf("subprotocol must be either binary or text\n");
+       }
+}
+#endif // WASM_USER_ADJUSTABLE
+
+EM_JS(bool, js_syncFS, (bool populate), {
+       FS.syncfs(populate, function(err) {
+               if(err)
+               {
+                       alert("FileSystem Save Error: " + err);
+                       return false;
+               }
+
+               alert("Filesystem Saved!");
+               return true;
+       });
+});
+static void em_savefs_f(cmd_state_t *cmd)
+{
+       Con_Printf("Saving Files\n");
+       js_syncFS(false);
+}
+
+void Sys_SDL_Shutdown(void)
+{
+       js_syncFS(false);
+       SDL_Quit();
+}
+
+// Sys_Abort early in startup might screw with automated
+// workflows or something if we show the dialog by default.
+static qbool nocrashdialog = true;
+void Sys_SDL_Dialog(const char *title, const char *string)
+{
+       if(!nocrashdialog)
+               SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, string, NULL);
+}
+
+// In a browser the clipboard can only be read asynchronously
+// doing this efficiently and cleanly requires JSPI
+// enable in makefile.inc with emcc option: -s JSPI
+/* TODO: enable this code when JSPI is enabled by default in browsers
+EM_ASYNC_JS(char *, js_getclipboard, (void),
+{
+       try
+       {
+               const text = await navigator.clipboard.readText();
+               return stringToNewUTF8(text);
+       }
+       catch (err)
+       {
+               return stringToNewUTF8("clipboard error: ", err);
+       }
+}); */
+EM_JS(char *, js_getclipboard, (void), {
+       return stringToNewUTF8("clipboard access requires JSPI which is not currently enabled.");
+});
+char *Sys_SDL_GetClipboardData (void)
+{
+       char *data = NULL;
+       char *cliptext;
+
+       // SDL_GetClipboardText() does nothing in a browser, see above
+       cliptext = js_getclipboard();
+       if (cliptext != NULL) {
+               size_t allocsize;
+               allocsize = min(MAX_INPUTLINE, strlen(cliptext) + 1);
+               data = (char *)Z_Malloc (allocsize);
+               dp_strlcpy (data, cliptext, allocsize);
+               free(cliptext);
+       }
+
+       return data;
+}
+
+void Sys_SDL_Init(void)
+{
+       if (SDL_Init(0) < 0)
+               Sys_Error("SDL_Init failed: %s\n", SDL_GetError());
+
+       // we don't know which systems we'll want to init, yet...
+       // COMMANDLINEOPTION: sdl: -nocrashdialog disables "Engine Error" crash dialog boxes
+       if(!Sys_CheckParm("-nocrashdialog"))
+               nocrashdialog = false;
+       
+       emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, EM_FALSE, em_on_resize);
+}
+
+void Sys_EM_Register_Commands(void)
+{
+#ifdef WASM_USER_ADJUSTABLE
+       Cmd_AddCommand(CF_SHARED, "em_ls", em_listfiles_f, "Lists Files in specified directory defaulting to the current working directory (Emscripten Only)");
+       Cmd_AddCommand(CF_SHARED, "em_upload", em_upload_f, "Upload file to specified directory defaulting to basedir (Emscripten Only)");
+       Cmd_AddCommand(CF_SHARED, "em_rm", em_rm_f, "Remove a file from game Filesystem (Emscripten Only)");
+       Cmd_AddCommand(CF_SHARED, "em_rmdir", em_rmdir_f, "Remove a directory from game Filesystem (Emscripten Only)");
+       Cmd_AddCommand(CF_SHARED, "em_mkdir", em_mkdir_f, "Make a directory in game Filesystem (Emscripten Only)");
+       Cmd_AddCommand(CF_SHARED, "em_mv", em_mv_f, "Rename or Move an item in game Filesystem (Emscripten only)");
+       Cmd_AddCommand(CF_SHARED, "em_wss", em_wss_f, "Set Websocket URL and Protocol (Emscripten Only)");
+#endif
+       Cmd_AddCommand(CF_SHARED, "em_save", em_savefs_f, "Save file changes to browser (Emscripten Only)");
+}
+
+qbool sys_supportsdlgetticks = true;
+unsigned int Sys_SDL_GetTicks(void)
+{
+       return SDL_GetTicks();
+}
+
+void Sys_SDL_Delay(unsigned int milliseconds)
+{
+       SDL_Delay(milliseconds);
+}
+
+int main(int argc, char *argv[])
+{
+       return Sys_Main(argc, argv);
+}
index 681ba54059793ab5a5dbb6a194e6225adb1c30c9..631b0da85de7451d9f2581b40ffff9d36f97bb81 100644 (file)
--- a/vid_sdl.c
+++ b/vid_sdl.c
@@ -1537,6 +1537,7 @@ On Xorg it returns the correct value.
                return;
 */
 
+       // __EMSCRIPTEN__ SDL_GL_SetSwapInterval() calls emscripten_set_main_loop_timing()
        if (SDL_GL_SetSwapInterval(vsyncwanted) >= 0)
                Con_DPrintf("Vsync %s\n", vsyncwanted ? "activated" : "deactivated");
        else
@@ -1711,10 +1712,8 @@ static qbool VID_InitModeGL(const viddef_mode_t *mode)
 {
        int windowflags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL;
        int i;
-#ifndef USE_GLES2
        // SDL usually knows best
        const char *drivername = NULL;
-#endif
 
        // video display selection (multi-monitor)
        Cvar_SetValueQuick(&vid_info_displaycount, SDL_GetNumVideoDisplays());
diff --git a/wasm/autoexec.cfg b/wasm/autoexec.cfg
new file mode 100644 (file)
index 0000000..b1164d5
--- /dev/null
@@ -0,0 +1,2 @@
+cl_particles_quake "1"
+
diff --git a/wasm/index.html b/wasm/index.html
new file mode 100644 (file)
index 0000000..3cbb0d2
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="">
+       <head>
+               <meta charset="UTF-8" />
+               <title>DarkPlaces Quake WASM</title>
+               <meta name="viewport" content="width=device-width,initial-scale=1" />
+               <meta name="description" content="" />
+               <link rel="icon" href="favicon.png">
+               <style>
+                       html, body {
+                               margin: 0;
+                               padding: 0;
+                       }
+               </style>
+       </head>
+       <body>
+               <canvas id="canvas" oncontextmenu="event.preventDefault()" style="width: auto; height: auto;"></canvas>
+               <script type='text/javascript'>
+                       var Module = {
+                               canvas: (function() { return document.getElementById('canvas'); })(),
+                               files: {
+                                       "id1/pak0.pak": "pak0.pak",
+                                       "id1/pak1.pak": "pak1.pak",
+                                       "id1/autoexec.cfg": "autoexec.cfg"
+                               },
+                               arguments: ['-game', 'id1', '+crosshair', '1']  // Arguments appended to the default arguments, can be used to change the gamedir
+                       };
+               </script>
+               <script type="text/javascript" src="darkplaces-wasm.js"></script>
+       </body>
+</html>
diff --git a/wasm/pre.js b/wasm/pre.js
new file mode 100644 (file)
index 0000000..ec05bf2
--- /dev/null
@@ -0,0 +1,101 @@
+if (!Object.hasOwn(Module, 'arguments')) {
+       Module['arguments'] = ['-basedir', '/game'];
+}
+else {
+       Module['arguments'] = ['-basedir', '/game'].concat(Module['arguments']);
+}
+
+Module['print'] = function(text) {
+       console.log(text);
+}
+
+Module['printErr'] = function(text) {
+       console.error(text);
+}
+
+
+Module['preRun'] = [
+       function()
+       {
+               function stdin(){
+                       return '\n';  // Return a newline/line feed character so the user is not prompted for input
+               };
+               FS.init(stdin, null, null); // null for both stdout and stderr
+
+               function createParentDirectory(filePath) {
+                       //
+                       // Split a filePath into parts, create the directory hierarchy
+                       //
+                       parts = filePath.split('/');
+                       for (let i = parts.length - 1; i > 0; i--) {
+                               localDir = '/game/' + parts.slice(0, -i).join('/')
+                               try {
+                                       FS.mkdir(localDir);
+                               }
+                               catch {
+                                       // Directory already exists
+                               }
+                       }
+               }
+
+               function startDownload(localPath, remotePath) {
+                       //
+                       // Return a promise of a file download
+                       //
+                       Module['addRunDependency'](localPath);  // Tell Emscripten about the dependency
+
+                       return fetch(remotePath)
+                               .then(response => {
+                                               return response.arrayBuffer();
+                               })
+                               .then(arrayBuffer => {
+                                       const buffer = new Uint8Array(arrayBuffer);
+                                       stream = FS.open("/game/" + localPath, "w");
+                                       FS.write(stream, buffer, 0, buffer.byteLength);
+                                       FS.close(stream);
+                                       console.log("Downloaded " + localPath);
+                                       Module['removeRunDependency'](localPath);  // Tells Emscripten we've finished the download
+                               });
+               }
+
+               function createBaseDir() {
+                       //
+                       // Creates the Quake basedir and mounts it to IDBFS
+                       //
+                       FS.mkdir('/game');
+                       //mounts IDBFS to where the game would save
+                       FS.mount(IDBFS, {}, '/home/web_user/');
+               }
+
+               function downloadGameFiles() {
+                       //
+                       // Download files specified in the Module.files object
+                       //
+                       createBaseDir();
+
+                       let downloads = [];
+                       for (const [localPath, remotePath] of Object.entries(Module.files)) {
+                               console.log("Downloading " + remotePath + " to " + localPath);
+
+                               createParentDirectory(localPath);
+
+                               downloads.push(
+                                       startDownload(localPath, remotePath)
+                               );
+                       }
+
+                       // Wait for downloads to finish, sync the filesystem, start the game
+                       Promise.all(downloads)
+                               .then(function(results) {
+                                       FS.syncfs(true, function (err) {  
+                                               assert(!err);
+                                               Module.callMain(Module.arguments);
+                                       });
+                               });
+               }
+
+               downloadGameFiles();
+       }
+];
+
+Module['noInitialRun'] = true;
diff --git a/wasm/preload/runhere b/wasm/preload/runhere
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/wasm/standalone-shell.html b/wasm/standalone-shell.html
new file mode 100644 (file)
index 0000000..b2779ca
--- /dev/null
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<!-- Thank You Stack Overflow! -->
+<head>
+       <meta charset="utf-8">
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8, width=device-width, initial-scale=1">
+       <title>DarkPlaces</title>
+</head>
+
+<body style="margin:0;padding:0">
+
+       <!-- Create the canvas that the C++ code will draw into -->
+       <canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas>
+
+       <!-- Allow the C++ to access the canvas element -->
+       <script type='text/javascript'>
+               var Module = {
+                       canvas: (function() { return document.getElementById('canvas'); })()
+               };
+               Module['preRun'] = []
+       </script>
+
+       <script type='text/javascript'>
+               const file_reader = new FileReader();
+               file_reader.addEventListener("load", readf);
+               function readf(event)
+               {
+                       // also heavily derivative of Riot's code on Stack Overflow cause I sure as hell don't understand it.
+                       // Riot used the MIT license.
+                       const uint8Arr = new Uint8Array(file_reader.result);
+                       console.log(currentname+fname);
+                       try
+                       {
+                               stream = FS.open(currentname+fname, 'w');
+                       }
+                       catch (error)
+                       {
+                               alert(error.toString() + "... Was that not a directory?");
+                               return;
+                       }
+
+                       FS.write(stream, uint8Arr, 0, uint8Arr.length, 0);
+                       FS.close(stream);
+                       alert("File Uploaded");
+               }
+
+               var currentname = "";
+               var fname = ""
+
+               function save_files()
+               {
+                       fname = this.files[0].name;
+                       file_reader.readAsArrayBuffer(this.files[0]);
+               };
+
+               var file_selector = document.createElement('input');
+               file_selector.setAttribute('type', 'file');
+               file_selector.addEventListener("change", save_files, false);
+       </script>
+
+       <!-- Where the script shall be -->
+       {{{ SCRIPT }}}
+
+</body>
+
+</html>
diff --git a/wasm/standaloneprejs.js b/wasm/standaloneprejs.js
new file mode 100644 (file)
index 0000000..30f8e90
--- /dev/null
@@ -0,0 +1,63 @@
+//current command in ascii decimal
+let currentcmd = [0,0,0] 
+let currentfile = "";
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+Module['print'] = function(text) { console.log(text); }
+
+Module['preRun'] = function()
+{
+       function stdin() { return 10 };
+       var stdout = null;
+       var stderr = null;
+       FS.init(stdin, stdout, stderr);
+       FS.mount(IDBFS, {}, "/home/web_user/");
+       FS.symlink("/home/web_user", "/save");
+}
+
+Module['noInitialRun'] = true
+
+document.addEventListener('click', (ev) => {
+       console.log("event is captured only once.");
+       args = []
+       if(window.location.href.indexOf("file://") > -1)
+       {
+               try
+               {
+                       args = args.concat(prompt("Enter command line arguments").split(" "))
+               }
+               catch (error)
+               {
+                       console.log("Error: ", error);
+                       console.log("Failed to concat extra arguments (likely passed nothing for the argument)")
+               }
+       }
+       else
+       {
+               parms = new URLSearchParams(window.location.search);
+               try
+               {
+                       args = args.concat(parms.get("args").split(" "))
+               }
+               catch (error)
+               {
+                       console.log("Error: ", error);
+                       console.log("Failed to concat extra arguments (likely passed nothing for the argument)")
+               }
+       }
+
+       FS.syncfs(true, function()
+       {
+               if(FS.analyzePath("/preload/runhere").exists)
+               {
+                       FS.symlink("/preload", "/home/web_user/games");
+                       args = args.concat(["-basedir", "/home/web_user/games"])
+               }
+               else
+               {
+                       args = args.concat(["-basedir", "/home/web_user/"])
+               }
+
+               Module.callMain(args);
+       });
+}, { once: true });