From 56014a27d6422bcac5c093531cb61cbad4ab7bea Mon Sep 17 00:00:00 2001 From: Joe Osborn Date: Wed, 19 Feb 2025 15:59:25 -0800 Subject: [PATCH] Enable pthreads on Emscripten (#17586) * workerized RA * Workerized (non-async) web player, using OPFS This patch eliminates the need for asyncify and uses modern filesystem APIs instead of the deprecated, unmaintained BrowserFS. This is a WIP patch because it won't fully work until these two Emscripten PRs land and are released: https://github.com/emscripten-core/emscripten/pull/23518 https://github.com/emscripten-core/emscripten/pull/23021 The former fixes an offscreen canvas context recreation bug, and the latter adds an equivalent to BrowserFS's XHR filesystem (but without the hazardous running-XHR-on-the-main-thread problem). The biggest issue is that local storage of users who were using the old version of the webplayer will be gone when they switch to the new webplayer. I don't have a good story for converting the old BrowserFS IDBFS contents into the new OPFS filesystem (the move is worth doing because OPFS supports seeking and reading only bits of a file, and because BrowserFS is dead). I've kept around the old libretro webplayer under pkg/emscripten/libretro-classic, and with these make flags you can build a non-workerized RA that uses asyncify to sleep as before: make -f Makefile.emscripten libretro=$CORE HAVE_WORKER=0 HAVE_WASMFS=0 PTHREAD=0 HAVE_AL=1 I also moved the default directory for core content on emscripten to not be a subdirectory of the local filesystem mount, because it's confusing to have a subdirectory that's lazily fetched and not mirrored to the local storage. I think it won't impact existing users of the classic web player because they already have a retroarch.cfg in place. * Get fetchfs working without manifest support * makefile fixes * fix scaling, remove zip dependency * Support asset/cheats/etc downloaders for emscripten - Add http transfer support for emscripten - At the task_http level, not the net_http level --- so no netplay or webdav. - Change default paths to be more like other platforms - Gives us smaller bundles and a faster boot time - Had to work around a task queue bug on Emscripten - I made the smallest possible change to do it, but it may be better to fix in rthread.c * Load an emscripten file_packager package on first run If no ozone assets are present, load a libretro_minimal package created using Emscripten's built-in file packager. * updated readme, removed indexer from wasmfs libretro-web * Put back zip dependency, load asset bundle into opfs on first run * fix upload path * Remove unused function * easy testing setup for two multithreaded conditions 1. make PROXY_TO_PTHREAD=1 (slower) 2. make PROXY_TO_PTHREAD=0 (bad audio, because doesn't sleep in openal.c) * Remove condition on sleep in openal also make input_driver check existence of drv->axis, drv->button before calling them. * Fix resizing under EGL * Don't force config file path on emscripten * Add time.h include to netplay, default HAVE_NETPLAYDISCOVERY to 0 * Remove nearly all proxied joypad calls under emscripten * Fix file uploads under firefox * Fix safari API uses, but Safari still hangs in OPFS filesystem mount I think this can be fixed by moving the backend creation off the main thread. * Move filesystem init into emscripten C entry point * Setup filesystems off of main thread * re-set default player to async Also improve Safari compatibility under proxy-to-pthread condition * Safari upload file fixes * Remove some excess prints * Fix typo --- Makefile.common | 12 +- Makefile.emscripten | 119 ++++-- config.def.h | 4 + emscripten/library_platform_emscripten.js | 22 -- frontend/drivers/platform_emscripten.c | 161 +++++++- gfx/drivers_context/emscriptenegl_ctx.c | 15 +- gfx/drivers_context/emscriptenwebgl_ctx.c | 298 +++++++++++++++ gfx/video_driver.c | 5 +- gfx/video_driver.h | 1 + input/drivers/rwebinput_input.c | 24 +- input/drivers_joypad/rwebpad_joypad.c | 79 ++-- input/input_driver.c | 12 +- libretro-common/include/retro_timers.h | 4 +- libretro-common/queues/task_queue.c | 12 +- network/netplay/netplay_frontend.c | 1 + pkg/emscripten/README.md | 58 ++- .../{libretro => libretro-thread}/embed.html | 0 pkg/emscripten/libretro-thread/index.html | 203 ++++++++++ pkg/emscripten/libretro-thread/libretro.css | 136 +++++++ pkg/emscripten/libretro-thread/libretro.js | 298 +++++++++++++++ .../libretro-thread/libretro.worker.js | 66 ++++ .../libretro-thread/zip-no-worker.min.js | 2 + pkg/emscripten/libretro/index.html | 8 +- pkg/emscripten/libretro/libretro.css | 16 +- pkg/emscripten/libretro/libretro.js | 7 +- tasks/task_http_emscripten.c | 360 ++++++++++++++++++ 26 files changed, 1748 insertions(+), 175 deletions(-) create mode 100644 gfx/drivers_context/emscriptenwebgl_ctx.c rename pkg/emscripten/{libretro => libretro-thread}/embed.html (100%) create mode 100644 pkg/emscripten/libretro-thread/index.html create mode 100644 pkg/emscripten/libretro-thread/libretro.css create mode 100644 pkg/emscripten/libretro-thread/libretro.js create mode 100644 pkg/emscripten/libretro-thread/libretro.worker.js create mode 100644 pkg/emscripten/libretro-thread/zip-no-worker.min.js create mode 100644 tasks/task_http_emscripten.c diff --git a/Makefile.common b/Makefile.common index 30e385a40f..8c04f8900d 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1536,7 +1536,10 @@ ifeq ($(HAVE_GL_CONTEXT), 1) endif ifeq ($(HAVE_EMSCRIPTEN), 1) - OBJ += gfx/drivers_context/emscriptenegl_ctx.o + ifeq ($(HAVE_EGL), 1) + OBJ += gfx/drivers_context/emscriptenegl_ctx.o + endif + OBJ += gfx/drivers_context/emscriptenwebgl_ctx.o endif ifeq ($(HAVE_MALI_FBDEV), 1) @@ -2203,11 +2206,16 @@ ifeq ($(HAVE_NETWORKING), 1) $(LIBRETRO_COMM_DIR)/net/net_socket.o \ core_updater_list.o \ network/natt.o \ - tasks/task_http.o \ tasks/task_netplay_lan_scan.o \ tasks/task_netplay_nat_traversal.o \ tasks/task_netplay_find_content.o + ifeq ($(HAVE_EMSCRIPTEN), 1) + OBJ += tasks/task_http_emscripten.o + else + OBJ += tasks/task_http.o + endif + ifeq ($(HAVE_MENU), 1) OBJ += tasks/task_pl_thumbnail_download.o endif diff --git a/Makefile.emscripten b/Makefile.emscripten index 164db24cf3..06e52ce247 100644 --- a/Makefile.emscripten +++ b/Makefile.emscripten @@ -19,12 +19,18 @@ HAVE_PATCH = 1 HAVE_DSP_FILTER = 1 HAVE_VIDEO_FILTER = 1 HAVE_OVERLAY = 1 +HAVE_NETWORKING = 1 +HAVE_LIBRETRODB = 1 +HAVE_COMPRESSION = 1 +HAVE_UPDATE_ASSETS = 1 +HAVE_ONLINE_UPDATER = 1 HAVE_GLSL = 1 HAVE_SCREENSHOTS = 1 HAVE_REWIND = 1 HAVE_AUDIOMIXER = 1 HAVE_CC_RESAMPLER = 1 -HAVE_EGL = 1 +HAVE_EGL ?= 1 +HAVE_OPENGLES = 1 HAVE_RJPEG = 0 HAVE_RPNG = 1 HAVE_EMSCRIPTEN = 1 @@ -48,6 +54,12 @@ HAVE_7ZIP = 1 HAVE_BSV_MOVIE = 1 HAVE_AL = 1 HAVE_CHD ?= 0 +HAVE_WASMFS ?= 0 +PROXY_TO_PTHREAD ?= 0 +HAVE_NETPLAYDISCOVERY ?= 0 + +DEFINES += -DHAVE_NETWORKING -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_ASSETS -DHAVE_COMPRESSION +DEFINES += -DHAVE_UPDATE_CORE_INFO # WARNING -- READ BEFORE ENABLING # The rwebaudio driver is known to have several audio bugs, such as @@ -61,12 +73,12 @@ HAVE_RWEBAUDIO = 0 GL_DEBUG ?= 0 # enable javascript filesystem tracking -FS_DEBUG = 1 +FS_DEBUG = 0 HAVE_OPENGLES ?= 1 HAVE_OPENGLES3 ?= 0 -ASYNC ?= 0 +ASYNC ?= 1 LTO ?= 0 PTHREAD ?= 0 @@ -92,59 +104,84 @@ _cmd_toggle_menu,_cmd_reload_config,_cmd_toggle_grab_mouse,_cmd_toggle_game_focu _cmd_set_volume,_cmd_set_shader,_cmd_cheat_set_code,_cmd_cheat_get_code,_cmd_cheat_toggle_index,_cmd_cheat_get_code_state,_cmd_cheat_realloc,\ _cmd_cheat_get_size,_cmd_cheat_apply_cheats -LIBS := -s USE_ZLIB=1 -LDFLAGS := -L. --no-heap-copy -s $(LIBS) -s STACK_SIZE=$(STACK_SIZE) -s INITIAL_MEMORY=$(INITIAL_HEAP) \ - -s EXPORTED_RUNTIME_METHODS=callMain,FS,PATH,ERRNO_CODES,stringToNewUTF8,UTF8ToString \ - -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS="$(EXPORTED_FUNCTIONS)" \ - -s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="libretro_$(subst -,_,$(LIBRETRO))" \ - --extern-pre-js emscripten/pre.js \ - --js-library emscripten/library_rwebcam.js \ - --js-library emscripten/library_platform_emscripten.js +EXPORTS := callMain,FS,PATH,ERRNO_CODES,ENV,stringToNewUTF8,UTF8ToString,Browser,GL -ifeq ($(HAVE_RWEBAUDIO), 1) - LDFLAGS += --js-library emscripten/library_rwebaudio.js - DEFINES += -DHAVE_RWEBAUDIO -endif -ifeq ($(HAVE_AL), 1) - LDFLAGS += -lopenal - DEFINES += -DHAVE_AL - override ASYNC = 1 -endif +LIBS := -s USE_ZLIB=1 -lbrowser.js -ifneq ($(PTHREAD), 0) - LDFLAGS += -s MAXIMUM_MEMORY=1073741824 -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD) - CFLAGS += -pthread - HAVE_THREADS=1 -else - HAVE_THREADS=0 -endif - -ifeq ($(ASYNC), 1) - LDFLAGS += -s ASYNCIFY=$(ASYNC) -s ASYNCIFY_STACK_SIZE=8192 - ifeq ($(DEBUG), 1) - LDFLAGS += -s ASYNCIFY_DEBUG=1 # -s ASYNCIFY_ADVISE +ifeq ($(HAVE_WASMFS), 1) + DEFINES += -DHAVE_WASMFS=1 + LIBS += -sWASMFS -sFORCE_FILESYSTEM=1 -lfetchfs.js -lopfs.js + EXPORTS := $(EXPORTS),FETCHFS,OPFS + ifeq ($(PTHREAD),0) + $(error ERROR: WASMFS requires threading support) endif endif +ifeq ($(PROXY_TO_PTHREAD),1) + LIBS += -sUSE_ES6_IMPORT_META=0 -sENVIRONMENT=worker,web + LIBS += -sPROXY_TO_PTHREAD -sOFFSCREENCANVAS_SUPPORT + DEFINES += -DUSE_OFFSCREENCANVAS=1 -DPROXY_TO_PTHREAD=1 +else + override ASYNC = 1 +endif + +ifeq ($(HAVE_SDL2), 1) + LIBS += -s USE_SDL=2 + DEFINES += -DHAVE_SDL2 +endif + + +LDFLAGS := -L. --no-heap-copy -s STACK_SIZE=$(STACK_SIZE) -s INITIAL_MEMORY=$(INITIAL_HEAP) \ + -s EXPORTED_RUNTIME_METHODS=$(EXPORTS) \ + -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS="$(EXPORTED_FUNCTIONS)" \ + -s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="libretro_$(subst -,_,$(LIBRETRO))" \ + -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 \ + --extern-pre-js emscripten/pre.js \ + --js-library emscripten/library_rwebcam.js \ + -gsource-map -g2 \ + --js-library emscripten/library_platform_emscripten.js + ifeq ($(HAVE_OPENGLES), 1) ifeq ($(HAVE_OPENGLES3), 1) - LDFLAGS += -s FULL_ES3=1 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 + LDFLAGS += -s FULL_ES3=1 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -lGL else - LDFLAGS += -s FULL_ES2=1 + LDFLAGS += -s FULL_ES2=1 -s MIN_WEBGL_VERSION=1 -s MAX_WEBGL_VERSION=2 -lGL endif endif ifeq ($(GL_DEBUG), 1) - LDFLAGS += -s GL_ASSERTIONS=1 -s GL_DEBUG=1 + LDFLAGS += -s GL_ASSERTIONS=1 -s GL_DEBUG=1 -DHAVE_GL_DEBUG_ES=1 endif ifeq ($(FS_DEBUG), 1) LDFLAGS += -s FS_DEBUG=1 endif -ifeq ($(HAVE_SDL2), 1) - LIBS += -s USE_SDL=2 - DEFINES += -DHAVE_SDL2 +ifeq ($(HAVE_RWEBAUDIO), 1) + LDFLAGS += --js-library emscripten/library_rwebaudio.js + DEFINES += -DHAVE_RWEBAUDIO +endif + +ifeq ($(HAVE_AL), 1) + LDFLAGS += -lopenal + DEFINES += -DHAVE_AL +endif + +ifneq ($(PTHREAD), 0) + LDFLAGS += -s WASM_MEM_MAX=1073741824 -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD) + CFLAGS += -pthread -s SHARED_MEMORY + HAVE_THREADS=1 +else + HAVE_THREADS=0 +endif + + +ifeq ($(ASYNC), 1) + DEFINES += -DEMSCRIPTEN_ASYNCIFY + LDFLAGS += -s ASYNCIFY=$(ASYNC) -s ASYNCIFY_STACK_SIZE=8192 + ifeq ($(DEBUG), 1) + LDFLAGS += -s ASYNCIFY_DEBUG=1 # -s ASYNCIFY_ADVISE + endif endif include Makefile.common @@ -183,8 +220,10 @@ RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ)) all: $(TARGET) -$(TARGET): $(RARCH_OBJ) $(libretro) - @$(if $(libretro), mv -f $(libretro) $(libretro_new),) +$(libretro_new) : $(libretro) + mv -f $(libretro) $(libretro_new) + +$(TARGET): $(RARCH_OBJ) $(libretro_new) @$(if $(Q), $(shell echo echo "LD $@ \ $(libretro_new) $(LIBS) $(LDFLAGS)"),) $(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro_new) $(LIBS) $(LDFLAGS) diff --git a/config.def.h b/config.def.h index 425abba800..f6c9948a10 100644 --- a/config.def.h +++ b/config.def.h @@ -1833,7 +1833,11 @@ #define DEFAULT_BUILDBOT_SERVER_URL "" #endif +#ifdef EMSCRIPTEN +#define DEFAULT_BUILDBOT_ASSETS_SERVER_URL "https://buildbot.libretro.com/assets/" +#else #define DEFAULT_BUILDBOT_ASSETS_SERVER_URL "http://buildbot.libretro.com/assets/" +#endif #define DEFAULT_DISCORD_APP_ID "475456035851599874" diff --git a/emscripten/library_platform_emscripten.js b/emscripten/library_platform_emscripten.js index 99dce4595c..f97c75c4f3 100644 --- a/emscripten/library_platform_emscripten.js +++ b/emscripten/library_platform_emscripten.js @@ -15,28 +15,6 @@ var LibraryPlatformEmscripten = { } }, - PlatformEmscriptenWatchCanvasSize: function() { - RPE.observer = new ResizeObserver(function(e) { - var width, height; - var entry = e.find(i => i.target == Module.canvas); - if (!entry) return; - if (entry.devicePixelContentBoxSize) { - width = entry.devicePixelContentBoxSize[0].inlineSize; - height = entry.devicePixelContentBoxSize[0].blockSize; - } else { - width = Math.round(entry.contentRect.width * window.devicePixelRatio); - height = Math.round(entry.contentRect.height * window.devicePixelRatio); - } - Module.setCanvasSize(width, height); - Module.print("Setting real canvas size: " + width + " x " + height); - }); - RPE.observer.observe(Module.canvas); - window.addEventListener("resize", function(e) { - RPE.observer.unobserve(Module.canvas); - RPE.observer.observe(Module.canvas); - }, false); - }, - PlatformEmscriptenPowerStateInit: function() { if (!navigator.getBattery) return; navigator.getBattery().then(function(battery) { diff --git a/frontend/drivers/platform_emscripten.c b/frontend/drivers/platform_emscripten.c index de8105898d..7a5710ca2f 100644 --- a/frontend/drivers/platform_emscripten.c +++ b/frontend/drivers/platform_emscripten.c @@ -17,7 +17,13 @@ #include #include +#if HAVE_WASMFS +#include +#endif #include +#include +#include +#include #include #include @@ -50,7 +56,33 @@ #include "../../audio/audio_driver.h" void emscripten_mainloop(void); -void PlatformEmscriptenWatchCanvasSize(void); +void PlatformEmscriptenWatchCanvasSize(void) { + MAIN_THREAD_ASYNC_EM_ASM( + RPE.observer = new ResizeObserver(function(_e) { + var container = Module.canvas.parentElement; + var width = container.offsetWidth; + var height = container.offsetHeight; + var w = Module.canvas.width; + var h = Module.canvas.height; + if (w == 0 || h == 0 || width == 0 || height == 0) { return; } + /* Module.print("Setting real canvas size: " + width + " x " + height); */ + var new_w = `${width}px`; + var new_h = `${height}px`; + if (Module.canvas.style.width != new_w || Module.canvas.style.height != new_h) { + Module.canvas.style.width = new_w; + Module.canvas.style.height = new_h; + } + if (!Module.canvas.controlTransferredOffscreen) { + Module.Browser.setCanvasSize(width, height); + } + }); + RPE.observer.observe(Module.canvas.parentElement); + window.addEventListener("resize", function(e) { + RPE.observer.unobserve(Module.canvas.parentElement); + RPE.observer.observe(Module.canvas.parentElement); + }, false); + ); +} void PlatformEmscriptenPowerStateInit(void); bool PlatformEmscriptenPowerStateGetSupported(void); int PlatformEmscriptenPowerStateGetDischargeTime(void); @@ -262,17 +294,101 @@ static void frontend_emscripten_get_env(int *argc, char *argv[], fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CACHE], "/tmp/", "retroarch", sizeof(g_defaults.dirs[DEFAULT_DIR_CACHE])); - /* history and main config */ + /* history */ strlcpy(g_defaults.dirs[DEFAULT_DIR_CONTENT_HISTORY], user_path, sizeof(g_defaults.dirs[DEFAULT_DIR_CONTENT_HISTORY])); - fill_pathname_join(g_defaults.path_config, user_path, - FILE_PATH_MAIN_CONFIG, sizeof(g_defaults.path_config)); #ifndef IS_SALAMANDER dir_check_defaults("custom.ini"); #endif } +typedef struct args { + int argc; + char **argv; +} args_t; +static bool retro_started = false; +static bool filesystem_ready = false; + +#if HAVE_WASMFS +void PlatformEmscriptenMountFilesystems(void *info) { + char *opfs_mount = getenv("OPFS"); + char *fetch_manifest = getenv("FETCH_MANIFEST"); + if(opfs_mount) { + int res; + printf("[OPFS] Mount OPFS at %s\n", opfs_mount); + backend_t opfs = wasmfs_create_opfs_backend(); + { + char *parent = strdup(opfs_mount); + path_parent_dir(parent, strlen(parent)); + if(!path_mkdir(parent)) { + printf("mkdir error %d\n",errno); + abort(); + } + free(parent); + } + res = wasmfs_create_directory(opfs_mount, 0777, opfs); + if(res) { + printf("[OPFS] error result %d\n",res); + if(errno) { + printf("[OPFS] errno %d\n",errno); + abort(); + } + abort(); + } + } + if(fetch_manifest) { + /* fetch_manifest should be a path to a manifest file. + manifest files have this format: + + URL PATH + URL PATH + URL PATH + ... + + Where URL may not contain spaces, but PATH may. + */ + int max_line_len = 1024; + printf("[FetchFS] read fetch manifest from %s\n",fetch_manifest); + FILE *file = fopen(fetch_manifest, O_RDONLY); + char *line = calloc(sizeof(char), max_line_len); + size_t len = 0; + while (getline(&line, &len, file) != -1) { + char *path = strstr(line, " "); + backend_t fetch; + int fd; + if (!path) { + printf("Manifest file has invalid line %s\n",line); + return; + } + *path = '\0'; + path += 1; + printf("Fetch %s from %s\n", path, line); + { + char *parent = strdup(path); + path_parent_dir(parent, strlen(parent)); + if(!path_mkdir(parent)) { + printf("mkdir error %d\n",errno); + abort(); + } + free(parent); + } + fetch = wasmfs_create_fetch_backend(line, 8*1024*1024); + fd = wasmfs_create_file(path, 0777, fetch); + close(fd); + } + fclose(file); + free(line); + } + filesystem_ready = true; +#if !PROXY_TO_PTHREAD + while (!retro_started) { + retro_sleep(1); + } +#endif +} +#endif /* HAVE_WASMFS */ + static enum frontend_powerstate frontend_emscripten_get_powerstate(int *seconds, int *percent) { enum frontend_powerstate ret = FRONTEND_POWERSTATE_NONE; @@ -303,17 +419,42 @@ static uint64_t frontend_emscripten_get_free_mem(void) return PlatformEmscriptenGetFreeMem(); } +void emscripten_bootup_mainloop(void *argptr) { + if(filesystem_ready) { + args_t *args = (args_t*)argptr; + emscripten_set_main_loop(emscripten_mainloop, 0, 0); + emscripten_set_main_loop_timing(EM_TIMING_RAF, 1); + rarch_main(args->argc, args->argv, NULL); + retro_started = true; + free(args); + } +} + int main(int argc, char *argv[]) { + args_t *args = calloc(sizeof(args_t), 1); + PlatformEmscriptenWatchCanvasSize(); PlatformEmscriptenPowerStateInit(); - EM_ASM({ - specialHTMLTargets["!canvas"] = Module.canvas; - }); - - emscripten_set_main_loop(emscripten_mainloop, 0, 0); - rarch_main(argc, argv, NULL); + emscripten_set_canvas_element_size("#canvas", 800, 600); + emscripten_set_element_css_size("#canvas", 800.0, 600.0); +#if HAVE_WASMFS +#if PROXY_TO_PTHREAD + { + PlatformEmscriptenMountFilesystems(NULL); + } +#else /* !PROXY_TO_PTHREAD */ + { + sthread_t *thread = sthread_create(PlatformEmscriptenMountFilesystems, NULL); + sthread_detach(thread); + } +#endif /* PROXY_TO_PTHREAD */ +#else /* !HAVE_WASMFS */ + filesystem_ready = true; +#endif /* HAVE_WASMFS */ + emscripten_set_main_loop_arg(emscripten_bootup_mainloop, (void *)args, 0, 0); + emscripten_set_main_loop_timing(EM_TIMING_RAF, 1); return 0; } diff --git a/gfx/drivers_context/emscriptenegl_ctx.c b/gfx/drivers_context/emscriptenegl_ctx.c index 797999227c..b1097498f1 100644 --- a/gfx/drivers_context/emscriptenegl_ctx.c +++ b/gfx/drivers_context/emscriptenegl_ctx.c @@ -52,7 +52,7 @@ static void gfx_ctx_emscripten_swap_interval(void *data, int interval) static void gfx_ctx_emscripten_get_canvas_size(int *width, int *height) { - EMSCRIPTEN_RESULT r = emscripten_get_canvas_element_size("!canvas", width, height); + EMSCRIPTEN_RESULT r = emscripten_get_canvas_element_size("#canvas", width, height); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -70,11 +70,10 @@ static void gfx_ctx_emscripten_check_window(void *data, bool *quit, emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data; gfx_ctx_emscripten_get_canvas_size(&input_width, &input_height); - + *resize = (emscripten->fb_width != input_width || emscripten->fb_height != input_height); *width = emscripten->fb_width = (unsigned)input_width; *height = emscripten->fb_height = (unsigned)input_height; *quit = false; - *resize = false; } static void gfx_ctx_emscripten_swap_buffers(void *data) @@ -94,9 +93,10 @@ static void gfx_ctx_emscripten_get_video_size(void *data, if (!emscripten) return; - - *width = emscripten->fb_width; - *height = emscripten->fb_height; + int w, h; + gfx_ctx_emscripten_get_canvas_size(&w, &h); + *width = w; + *height = h; } static bool gfx_ctx_emscripten_get_metrics(void *data, @@ -124,11 +124,9 @@ static void gfx_ctx_emscripten_destroy(void *data) if (!emscripten) return; - #ifdef HAVE_EGL egl_destroy(&emscripten->egl); #endif - free(data); } @@ -191,7 +189,6 @@ static void *gfx_ctx_emscripten_init(void *video_driver) #endif return emscripten; - error: gfx_ctx_emscripten_destroy(video_driver); return NULL; diff --git a/gfx/drivers_context/emscriptenwebgl_ctx.c b/gfx/drivers_context/emscriptenwebgl_ctx.c new file mode 100644 index 0000000000..32702a6510 --- /dev/null +++ b/gfx/drivers_context/emscriptenwebgl_ctx.c @@ -0,0 +1,298 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2017 - Daniel De Matteis + * Copyright (C) 2012-2015 - Michael Lelli + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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 RetroArch. + * If not, see . + */ + +#include +#include +#include + +#include +#include + +#ifdef HAVE_CONFIG_H +#include "../../config.h" +#endif + +#include "../../retroarch.h" +#include "../../verbosity.h" + +typedef struct +{ + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx; + unsigned fb_width; + unsigned fb_height; +} emscripten_ctx_data_t; + +static void gfx_ctx_emscripten_webgl_swap_interval(void *data, int interval) +{ + if (interval == 0) + emscripten_set_main_loop_timing(EM_TIMING_SETIMMEDIATE, 0); + else + emscripten_set_main_loop_timing(EM_TIMING_RAF, interval); +} + +static void gfx_ctx_emscripten_webgl_get_canvas_size(int *width, int *height) +{ + EmscriptenFullscreenChangeEvent fullscreen_status; + bool is_fullscreen = false; + EMSCRIPTEN_RESULT r = emscripten_get_fullscreen_status(&fullscreen_status); + + if (r == EMSCRIPTEN_RESULT_SUCCESS) + { + if (fullscreen_status.isFullscreen) + { + is_fullscreen = true; + *width = fullscreen_status.screenWidth; + *height = fullscreen_status.screenHeight; + } + } + if (!is_fullscreen) + { + double w, h; + r = emscripten_get_element_css_size("#canvas", &w, &h); + *width = (int)w; + *height = (int)h; + + if (r != EMSCRIPTEN_RESULT_SUCCESS) + { + *width = 800; + *height = 600; + RARCH_ERR("[EMSCRIPTEN/WebGL]: Could not get screen dimensions: %d\n",r); + } + } +} + +static void gfx_ctx_emscripten_webgl_check_window(void *data, bool *quit, + bool *resize, unsigned *width, unsigned *height) +{ + int input_width=0; + int input_height=0; + emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data; + + gfx_ctx_emscripten_webgl_get_canvas_size(&input_width, &input_height); + *width = (unsigned)input_width; + *height = (unsigned)input_height; + *resize = (*width != emscripten->fb_width || *height != emscripten->fb_height); + emscripten->fb_width = *width; + emscripten->fb_height = *height; + *quit = false; +} + +static void gfx_ctx_emscripten_webgl_swap_buffers(void *data) +{ +#ifdef USE_OFFSCREENCANVAS + emscripten_webgl_commit_frame(); +#endif +} + +static void gfx_ctx_emscripten_webgl_get_video_size(void *data, + unsigned *width, unsigned *height) +{ + emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data; + int s_width, s_height; + if (!emscripten) + return; + gfx_ctx_emscripten_webgl_get_canvas_size(&s_width, &s_height); + *width = (unsigned)s_width; + *height = (unsigned)s_height; +} + +static void gfx_ctx_emscripten_webgl_destroy(void *data) +{ + emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data; + + if (!emscripten) + return; + + emscripten_webgl_destroy_context(emscripten->ctx); + + free(data); +} + +static void *gfx_ctx_emscripten_webgl_init(void *video_driver) +{ + int width, height; + emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*) + calloc(1, sizeof(*emscripten)); + + EmscriptenWebGLContextAttributes attrs={0}; + emscripten_webgl_init_context_attributes(&attrs); + attrs.alpha = false; + attrs.depth = true; + attrs.stencil = true; + attrs.antialias = false; + attrs.powerPreference = EM_WEBGL_POWER_PREFERENCE_HIGH_PERFORMANCE; + attrs.majorVersion = 2; + attrs.minorVersion = 0; + attrs.enableExtensionsByDefault = true; +#ifdef USE_OFFSCREENCANVAS + attrs.explicitSwapControl = true; +#else + attrs.explicitSwapControl = false; +#endif + attrs.renderViaOffscreenBackBuffer = false; + attrs.proxyContextToMainThread = EMSCRIPTEN_WEBGL_CONTEXT_PROXY_DISALLOW; + + if (!emscripten) + return NULL; + + emscripten->ctx = emscripten_webgl_create_context("#canvas", &attrs); + if(!emscripten->ctx) { + RARCH_ERR("[EMSCRIPTEN/WEBGL]: Failed to initialize webgl\n"); + goto error; + } + emscripten_webgl_get_drawing_buffer_size(emscripten->ctx, &width, &height); + emscripten_webgl_make_context_current(emscripten->ctx); + emscripten->fb_width = (unsigned)width; + emscripten->fb_height = (unsigned)height; + + return emscripten; + +error: + gfx_ctx_emscripten_webgl_destroy(video_driver); + return NULL; +} + +static bool gfx_ctx_emscripten_webgl_set_video_mode(void *data, + unsigned width, unsigned height, + bool fullscreen) +{ + emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data; + EMSCRIPTEN_RESULT r; + if(!emscripten || !emscripten->ctx) return false; + + if (width != 0 && height != 0) { + r = emscripten_set_canvas_element_size("#canvas", + (int)width, (int)height); + + if (r != EMSCRIPTEN_RESULT_SUCCESS) { + RARCH_ERR("[EMSCRIPTEN/WebGL]: error resizing canvas: %d\n", r); + return false; + } + } + emscripten->fb_width = width; + emscripten->fb_height = height; + + return true; +} + +bool gfx_ctx_emscripten_webgl_set_resize(void *data, unsigned width, unsigned height) { + emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data; + EMSCRIPTEN_RESULT r; + if(!emscripten || !emscripten->ctx) return false; + r = emscripten_set_canvas_element_size("#canvas", + (int)width, (int)height); + if (r != EMSCRIPTEN_RESULT_SUCCESS) { + RARCH_ERR("[EMSCRIPTEN/WebGL]: error resizing canvas: %d\n", r); + return false; + } + return true; +} + +static enum gfx_ctx_api gfx_ctx_emscripten_webgl_get_api(void *data) { return GFX_CTX_OPENGL_ES_API; } + +static bool gfx_ctx_emscripten_webgl_bind_api(void *data, + enum gfx_ctx_api api, unsigned major, unsigned minor) +{ + return true; +} + +static void gfx_ctx_emscripten_webgl_input_driver(void *data, + const char *name, + input_driver_t **input, void **input_data) +{ + void *rwebinput = input_driver_init_wrap(&input_rwebinput, name); + *input = rwebinput ? &input_rwebinput : NULL; + *input_data = rwebinput; +} + +static bool gfx_ctx_emscripten_webgl_get_metrics(void *data, + enum display_metric_types type, float *value) +{ + switch (type) + { + // there is no way to get the actual DPI in emscripten, so return a standard value instead. + // this is needed for menu touch/pointer swipe scrolling to work. + case DISPLAY_METRIC_DPI: + *value = 150.0f; + break; + + default: + *value = 0.0f; + return false; + } + + return true; +} + +static bool gfx_ctx_emscripten_webgl_has_focus(void *data) { + emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data; + return emscripten && emscripten->ctx; +} + +static bool gfx_ctx_emscripten_webgl_suppress_screensaver(void *data, bool enable) { return false; } + +static float gfx_ctx_emscripten_webgl_translate_aspect(void *data, + unsigned width, unsigned height) { return (float)width / height; } + +static void gfx_ctx_emscripten_webgl_bind_hw_render(void *data, bool enable) +{ + emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data; + emscripten_webgl_make_context_current(emscripten->ctx); +} + +static uint32_t gfx_ctx_emscripten_webgl_get_flags(void *data) +{ + uint32_t flags = 0; + BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_GLSL); + return flags; +} + +static void gfx_ctx_emscripten_webgl_set_flags(void *data, uint32_t flags) { } + +const gfx_ctx_driver_t gfx_ctx_emscripten_webgl = { + gfx_ctx_emscripten_webgl_init, + gfx_ctx_emscripten_webgl_destroy, + gfx_ctx_emscripten_webgl_get_api, + gfx_ctx_emscripten_webgl_bind_api, + gfx_ctx_emscripten_webgl_swap_interval, + gfx_ctx_emscripten_webgl_set_video_mode, + gfx_ctx_emscripten_webgl_get_video_size, + NULL, /* get_refresh_rate */ + NULL, /* get_video_output_size */ + NULL, /* get_video_output_prev */ + NULL, /* get_video_output_next */ + gfx_ctx_emscripten_webgl_get_metrics, + gfx_ctx_emscripten_webgl_translate_aspect, + NULL, /* update_title */ + gfx_ctx_emscripten_webgl_check_window, + gfx_ctx_emscripten_webgl_set_resize, /* set_resize */ + gfx_ctx_emscripten_webgl_has_focus, + gfx_ctx_emscripten_webgl_suppress_screensaver, + false, + gfx_ctx_emscripten_webgl_swap_buffers, + gfx_ctx_emscripten_webgl_input_driver, + NULL, + NULL, + NULL, + NULL, + "webgl_emscripten", + gfx_ctx_emscripten_webgl_get_flags, + gfx_ctx_emscripten_webgl_set_flags, + gfx_ctx_emscripten_webgl_bind_hw_render, + NULL, /* get_context_data */ + NULL /* make_current */ +}; diff --git a/gfx/video_driver.c b/gfx/video_driver.c index a891b6f914..8cc7c1a741 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -162,8 +162,11 @@ static const gfx_ctx_driver_t *gfx_ctx_gl_drivers[] = { #ifdef HAVE_OSMESA &gfx_ctx_osmesa, #endif -#ifdef EMSCRIPTEN +#if (defined(EMSCRIPTEN) && defined(HAVE_EGL)) &gfx_ctx_emscripten, +#endif +#ifdef EMSCRIPTEN + &gfx_ctx_emscripten_webgl, #endif &gfx_ctx_null, NULL diff --git a/gfx/video_driver.h b/gfx/video_driver.h index 26bbee6421..a5cb80d986 100644 --- a/gfx/video_driver.h +++ b/gfx/video_driver.h @@ -1384,6 +1384,7 @@ extern const gfx_ctx_driver_t gfx_ctx_cgl; extern const gfx_ctx_driver_t gfx_ctx_cocoagl; extern const gfx_ctx_driver_t gfx_ctx_cocoavk; extern const gfx_ctx_driver_t gfx_ctx_emscripten; +extern const gfx_ctx_driver_t gfx_ctx_emscripten_webgl; extern const gfx_ctx_driver_t gfx_ctx_opendingux_fbdev; extern const gfx_ctx_driver_t gfx_ctx_khr_display; extern const gfx_ctx_driver_t gfx_ctx_gdi; diff --git a/input/drivers/rwebinput_input.c b/input/drivers/rwebinput_input.c index 67823f16e2..914aa039d9 100644 --- a/input/drivers/rwebinput_input.c +++ b/input/drivers/rwebinput_input.c @@ -417,7 +417,7 @@ static void *rwebinput_input_init(const char *joypad_driver) rwebinput_generate_lut(); r = emscripten_set_keydown_callback( - "!canvas", rwebinput, false, + "#canvas", rwebinput, false, rwebinput_keyboard_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -426,7 +426,7 @@ static void *rwebinput_input_init(const char *joypad_driver) } r = emscripten_set_keyup_callback( - "!canvas", rwebinput, false, + "#canvas", rwebinput, false, rwebinput_keyboard_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -435,7 +435,7 @@ static void *rwebinput_input_init(const char *joypad_driver) } r = emscripten_set_keypress_callback( - "!canvas", rwebinput, false, + "#canvas", rwebinput, false, rwebinput_keyboard_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -443,7 +443,7 @@ static void *rwebinput_input_init(const char *joypad_driver) "[EMSCRIPTEN/INPUT] failed to create keypress callback: %d\n", r); } - r = emscripten_set_mousedown_callback("!canvas", rwebinput, false, + r = emscripten_set_mousedown_callback("#canvas", rwebinput, false, rwebinput_mouse_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -451,7 +451,7 @@ static void *rwebinput_input_init(const char *joypad_driver) "[EMSCRIPTEN/INPUT] failed to create mousedown callback: %d\n", r); } - r = emscripten_set_mouseup_callback("!canvas", rwebinput, false, + r = emscripten_set_mouseup_callback("#canvas", rwebinput, false, rwebinput_mouse_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -459,7 +459,7 @@ static void *rwebinput_input_init(const char *joypad_driver) "[EMSCRIPTEN/INPUT] failed to create mouseup callback: %d\n", r); } - r = emscripten_set_mousemove_callback("!canvas", rwebinput, false, + r = emscripten_set_mousemove_callback("#canvas", rwebinput, false, rwebinput_mouse_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -468,7 +468,7 @@ static void *rwebinput_input_init(const char *joypad_driver) } r = emscripten_set_wheel_callback( - "!canvas", rwebinput, false, + "#canvas", rwebinput, false, rwebinput_wheel_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -476,7 +476,7 @@ static void *rwebinput_input_init(const char *joypad_driver) "[EMSCRIPTEN/INPUT] failed to create wheel callback: %d\n", r); } - r = emscripten_set_touchstart_callback("!canvas", rwebinput, false, + r = emscripten_set_touchstart_callback("#canvas", rwebinput, false, rwebinput_touch_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -484,7 +484,7 @@ static void *rwebinput_input_init(const char *joypad_driver) "[EMSCRIPTEN/INPUT] failed to create touchstart callback: %d\n", r); } - r = emscripten_set_touchend_callback("!canvas", rwebinput, false, + r = emscripten_set_touchend_callback("#canvas", rwebinput, false, rwebinput_touch_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -492,7 +492,7 @@ static void *rwebinput_input_init(const char *joypad_driver) "[EMSCRIPTEN/INPUT] failed to create touchend callback: %d\n", r); } - r = emscripten_set_touchmove_callback("!canvas", rwebinput, false, + r = emscripten_set_touchmove_callback("#canvas", rwebinput, false, rwebinput_touch_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -500,7 +500,7 @@ static void *rwebinput_input_init(const char *joypad_driver) "[EMSCRIPTEN/INPUT] failed to create touchmove callback: %d\n", r); } - r = emscripten_set_touchcancel_callback("!canvas", rwebinput, false, + r = emscripten_set_touchcancel_callback("#canvas", rwebinput, false, rwebinput_touch_cb); if (r != EMSCRIPTEN_RESULT_SUCCESS) { @@ -819,7 +819,7 @@ static void rwebinput_input_poll(void *data) static void rwebinput_grab_mouse(void *data, bool state) { if (state) - emscripten_request_pointerlock("!canvas", EM_TRUE); + emscripten_request_pointerlock("#canvas", EM_TRUE); else emscripten_exit_pointerlock(); } diff --git a/input/drivers_joypad/rwebpad_joypad.c b/input/drivers_joypad/rwebpad_joypad.c index 1c63cf690f..72777334fd 100644 --- a/input/drivers_joypad/rwebpad_joypad.c +++ b/input/drivers_joypad/rwebpad_joypad.c @@ -26,6 +26,12 @@ #include "../../verbosity.h" #define CLAMPDOUBLE(x) MIN(1.0, MAX(-1.0, (x))) +#define NUM_BUTTONS 64 +#define NUM_AXES 64 + +/* TODO/FIXME - static globals */ +static struct EmscriptenGamepadEvent _pads[DEFAULT_MAX_PADS]; +static bool _live_pads[DEFAULT_MAX_PADS] = {0}; static EM_BOOL rwebpad_gamepad_cb(int event_type, const EmscriptenGamepadEvent *gamepad_event, void *user_data) @@ -36,6 +42,8 @@ static EM_BOOL rwebpad_gamepad_cb(int event_type, switch (event_type) { case EMSCRIPTEN_EVENT_GAMEPADCONNECTED: + _pads[gamepad_event->index] = *gamepad_event; + _live_pads[gamepad_event->index] = true; input_autoconfigure_connect( gamepad_event->id, /* name */ NULL, /* display name */ @@ -45,6 +53,7 @@ static EM_BOOL rwebpad_gamepad_cb(int event_type, pid); /* pid */ break; case EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED: + _live_pads[gamepad_event->index] = false; input_autoconfigure_disconnect(gamepad_event->index, rwebpad_joypad.ident); break; @@ -68,28 +77,23 @@ static void *rwebpad_joypad_init(void *data) r = emscripten_set_gamepaddisconnected_callback(NULL, false, rwebpad_gamepad_cb); - return (void*)-1; + return (void*)(-1); } static const char *rwebpad_joypad_name(unsigned pad) { - static EmscriptenGamepadEvent gamepad_state; - EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status(pad, &gamepad_state); - if (r == EMSCRIPTEN_RESULT_SUCCESS) - return gamepad_state.id; - return ""; + if (pad >= DEFAULT_MAX_PADS || !_live_pads[pad]) { + return ""; + } + return _pads[pad].id; } static int32_t rwebpad_joypad_button(unsigned port, uint16_t joykey) { EmscriptenGamepadEvent gamepad_state; - EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status( - port, &gamepad_state); - - if (port >= DEFAULT_MAX_PADS) - return 0; - if (r != EMSCRIPTEN_RESULT_SUCCESS) + if (port >= DEFAULT_MAX_PADS || !_live_pads[port]) return 0; + gamepad_state = _pads[port]; if (joykey < gamepad_state.numButtons) return gamepad_state.digitalButton[joykey]; return 0; @@ -98,21 +102,17 @@ static int32_t rwebpad_joypad_button(unsigned port, uint16_t joykey) static void rwebpad_joypad_get_buttons(unsigned port_num, input_bits_t *state) { EmscriptenGamepadEvent gamepad_state; - EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status( - port_num, &gamepad_state); - - if (r == EMSCRIPTEN_RESULT_SUCCESS) - { - unsigned i; - - for (i = 0; i < gamepad_state.numButtons; i++) - { - if (gamepad_state.digitalButton[i]) - BIT256_SET_PTR(state, i); - } - } - else + unsigned i; + if (port_num >= DEFAULT_MAX_PADS || !_live_pads[port_num]) { BIT256_CLEAR_ALL_PTR(state); + return; + } + gamepad_state = _pads[port_num]; + for (i = 0; i < gamepad_state.numButtons; i++) + { + if (gamepad_state.digitalButton[i]) + BIT256_SET_PTR(state, i); + } } static int16_t rwebpad_joypad_axis_state( @@ -138,11 +138,10 @@ static int16_t rwebpad_joypad_axis_state( static int16_t rwebpad_joypad_axis(unsigned port, uint32_t joyaxis) { - EmscriptenGamepadEvent gamepad_state; - EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status(port, &gamepad_state); - if (r != EMSCRIPTEN_RESULT_SUCCESS) + if (port >= DEFAULT_MAX_PADS || !_live_pads[port]) { return 0; - return rwebpad_joypad_axis_state(&gamepad_state, port, joyaxis); + } + return rwebpad_joypad_axis_state(&_pads[port], port, joyaxis); } static int16_t rwebpad_joypad_state( @@ -154,13 +153,9 @@ static int16_t rwebpad_joypad_state( EmscriptenGamepadEvent gamepad_state; int16_t ret = 0; uint16_t port_idx = joypad_info->joy_idx; - EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status( - port_idx, &gamepad_state); - if (r != EMSCRIPTEN_RESULT_SUCCESS) + if (port_idx >= DEFAULT_MAX_PADS || !_live_pads[port]) return 0; - if (port_idx >= DEFAULT_MAX_PADS) - return 0; - + gamepad_state = _pads[port]; for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++) { /* Auto-binds are per joypad, not per user. */ @@ -187,16 +182,16 @@ static int16_t rwebpad_joypad_state( static void rwebpad_joypad_poll(void) { emscripten_sample_gamepad_data(); + for (int i = 0; i < DEFAULT_MAX_PADS; i++) { + if (_live_pads[i]) { + emscripten_get_gamepad_status(i, &_pads[i]); + } + } } static bool rwebpad_joypad_query_pad(unsigned pad) { - EmscriptenGamepadEvent gamepad_state; - EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status(pad, &gamepad_state); - - if (r == EMSCRIPTEN_RESULT_SUCCESS) - return gamepad_state.connected == EM_TRUE; - return false; + return _live_pads[pad]; } static void rwebpad_joypad_destroy(void) { } diff --git a/input/input_driver.c b/input/input_driver.c index 47e3571f77..e69f894a56 100644 --- a/input/input_driver.c +++ b/input/input_driver.c @@ -1076,16 +1076,16 @@ static int16_t input_joypad_analog_axis( ? joypad_info->auto_binds[ident_y_plus].joyaxis : bind_y_plus->joyaxis; /* normalized magnitude for radial scaled analog deadzone */ - if (x_axis_plus != AXIS_NONE) + if (x_axis_plus != AXIS_NONE && drv->axis) x = drv->axis( joypad_info->joy_idx, x_axis_plus); - if (x_axis_minus != AXIS_NONE) + if (x_axis_minus != AXIS_NONE && drv->axis) x += drv->axis(joypad_info->joy_idx, x_axis_minus); - if (y_axis_plus != AXIS_NONE) + if (y_axis_plus != AXIS_NONE && drv->axis) y = drv->axis( joypad_info->joy_idx, y_axis_plus); - if (y_axis_minus != AXIS_NONE) + if (y_axis_minus != AXIS_NONE && drv->axis) y += drv->axis( joypad_info->joy_idx, y_axis_minus); normal_mag = (1.0f / 0x7fff) * sqrt(x * x + y * y); @@ -1113,9 +1113,9 @@ static int16_t input_joypad_analog_axis( uint16_t key_plus = (bind_plus->joykey == NO_BTN) ? joypad_info->auto_binds[ident_plus].joykey : bind_plus->joykey; - if (drv->button(joypad_info->joy_idx, key_plus)) + if (drv->button && drv->button(joypad_info->joy_idx, key_plus)) res = 0x7fff; - if (drv->button(joypad_info->joy_idx, key_minus)) + if (drv->button && drv->button(joypad_info->joy_idx, key_minus)) res += -0x7fff; } diff --git a/libretro-common/include/retro_timers.h b/libretro-common/include/retro_timers.h index 96041255a3..e519ad617f 100644 --- a/libretro-common/include/retro_timers.h +++ b/libretro-common/include/retro_timers.h @@ -39,7 +39,7 @@ #include #elif defined(_3DS) #include <3ds.h> -#elif defined(EMSCRIPTEN) +#elif (defined(EMSCRIPTEN) && defined(EMSCRIPTEN_ASYNCIFY)) #include #else #include @@ -100,7 +100,7 @@ static int nanosleepDOS(const struct timespec *rqtp, struct timespec *rmtp) #define retro_sleep(msec) (usleep(1000 * (msec))) #elif defined(WIIU) #define retro_sleep(msec) (OSSleepTicks(ms_to_ticks((msec)))) -#elif defined(EMSCRIPTEN) +#elif defined(EMSCRIPTEN) && defined(EMSCRIPTEN_ASYNCIFY) #define retro_sleep(msec) (emscripten_sleep(msec)) #else static INLINE void retro_sleep(unsigned msec) diff --git a/libretro-common/queues/task_queue.c b/libretro-common/queues/task_queue.c index 35614770df..2debff8538 100644 --- a/libretro-common/queues/task_queue.c +++ b/libretro-common/queues/task_queue.c @@ -35,7 +35,9 @@ #ifdef HAVE_THREADS #include #endif - +#ifdef EMSCRIPTEN +#include +#endif #ifdef HAVE_GCD #include #endif @@ -522,8 +524,14 @@ static void threaded_worker(void *userdata) } slock_unlock(running_lock); - task->handler(task); +#ifdef EMSCRIPTEN + /* Workaround emscripten pthread bug where not parking the + thread will prevent other important stuff from + happening. Maybe due to lack of signals implementation in + emscripten's pthreads? */ + retro_sleep(1); +#endif slock_lock(property_lock); finished = ((task->flags & RETRO_TASK_FLG_FINISHED) > 0) ? true : false; diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 8902e401fc..9bc9ec7e77 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -28,6 +28,7 @@ #endif #include +#include #include #include diff --git a/pkg/emscripten/README.md b/pkg/emscripten/README.md index ba5787d48f..1295bdd7b4 100644 --- a/pkg/emscripten/README.md +++ b/pkg/emscripten/README.md @@ -2,7 +2,7 @@ The RetroArch Web Player is RetroArch compiled through [Emscripten](https://emscripten.org/). The following outlines how to compile RetroArch using Emscripten, and running it in your browser. -## Compiling +## Compiling the Single-Threaded Player To compile RetroArch with Emscripten, you'll first have to [download and install the Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html) at 3.1.46: @@ -35,7 +35,7 @@ cp fceumm_libretro.{js,wasm} pkg/emscripten/libretro The emscripten build in the retroarch tree does not contain the necessary web assets for a complete RetroArch installation. You'll need the asset package from the latest emscripten nightly build ( https://buildbot.libretro.com/nightly/emscripten/ ); take its `assets/` folder and put it into `pkg/emscripten/libretro`. This `assets/` folder should contain a `frontend/` directory and a `cores/` directory. -If you're building your own frontend asset bundle (i.e. modifying `frontend/bundle/`), you'll need to turn the bundle into zipped partfiles. Open a terminal in `assets/frontend` and `zip -r9 bundle.zip bundle && split -b 30M bundle.zip bundle.zip.` (this should work on Mac and Linux, please file a PR with instructions for Windows). +If you're building your own frontend asset bundle (i.e. modifying `frontend/bundle/`), you'll need to turn the bundle into zipped partfiles. Open a terminal in `assets/frontend/` and `zip -r9 bundle.zip bundle && cd .. && split -b 30M bundle.zip bundle.zip.` (this should work on Mac and Linux, please file a PR with instructions for Windows). If you want to add more built-in core content files to `assets/cores`, you need to re-run the indexer script: @@ -83,11 +83,11 @@ emmake make -f Makefile platform=emscripten cp melonds_libretro_emscripten.bc ~/retroarch/RetroArch/libretro_emscripten.bc ``` -Now build the frontend with the pthreads env variable: (2 is the number of workers this can be any integer) +Now build the frontend with the pthreads env variable: (2 is the number of workers; this can be any integer, but many browsers limit the number of workers) ``` cd ~/retroarch/RetroArch -pthread=2 emmake make -f Makefile.emscripten LIBRETRO=melonds && cp melonds_libretro.* pkg/emscripten/libretro +emmake make -f Makefile.emscripten LIBRETRO=melonds PTHREAD=2 && cp melonds_libretro.* pkg/emscripten/libretro ``` Your resulting output will be located in: @@ -95,12 +95,11 @@ Your resulting output will be located in: ``` ~/retroarch/RetroArch/pkg/emscripten/libretro/melonds_libretro.js ~/retroarch/RetroArch/pkg/emscripten/libretro/melonds_libretro.wasm -~/retroarch/RetroArch/pkg/emscripten/libretro/melonds_libretro.worker.js ``` ## Setting up your webserver (Threaded) -Unless loading from `localhost` you will need to server the content from an HTTPS endpoint with a valid SSL certificate. This is a security limitation imposed by the browser. Along with that you will need to set content control policies with special headers in your server: +To support multithreaded builds, you will need to serve the content from an HTTPS endpoint with a valid SSL certificate. This is a security limitation imposed by the browser. Along with that you will need to set content control policies with special headers in your server: In Nodejs with express: @@ -108,6 +107,7 @@ In Nodejs with express: app.use(function(req, res, next) { res.header("Cross-Origin-Embedder-Policy", "require-corp"); res.header("Cross-Origin-Opener-Policy", "same-origin"); + res.header("Cross-Origin-Resource-Policy", "same-origin"); next(); }); ``` @@ -117,4 +117,50 @@ In NGINX: (site config under `server {`) ``` add_header Cross-Origin-Opener-Policy same-origin; add_header Cross-Origin-Embedder-Policy require-corp; + add_header Cross-Origin-Resource-Policy same-origin; ``` + +Node http-server: + +``` +http-server . -S \ + --header "Cross-Origin-Opener-Policy: same-origin" \ + --header "Cross-Origin-Embedder-Policy: require-corp" \ + --header "Cross-Origin-Resource-Policy: same-origin" +``` + +# Compiling the Multi-Threaded Frontend + +To compile the multi-threaded RetroArch frontend with Emscripten and make use of wasmfs and other features, you'll first have to [download and install the Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html). Currently, we need the "top of tree" or latest version of Emscripten: + +``` +git clone https://github.com/emscripten-core/emsdk.git +cd emsdk +./emsdk install tot +./emsdk activate tot +source emsdk_env.sh +``` + +After emsdk is installed you will need to build an emulator core, move that output into Retroarch, and use helper scripts to produce web ready assets, in this example we will be building [https://github.com/libretro/libretro-fceumm](https://github.com/libretro/libretro-fceumm): + +``` +mkdir ~/retroarch +cd ~/retroarch +git clone https://github.com/libretro/libretro-fceumm.git +cd libretro-fceumm +emmake make -f Makefile.libretro platform=emscripten +git clone https://github.com/libretro/RetroArch.git ~/retroarch/RetroArch +cp ~/retroarch/libretro-fceumm/fceumm_libretro_emscripten.bc ~/retroarch/RetroArch/libretro_emscripten.bc + +cd ~/retroarch +emmake make -f Makefile.emscripten LIBRETRO=fceumm PROXY_TO_PTHREAD=1 PTHREAD=4 HAVE_WASMFS=1 ASYNC=0 HAVE_EGL=0 -j all +cp fceumm_libretro.{js,wasm} pkg/emscripten/libretro-thread +``` + +## Dependencies + +The emscripten build in the retroarch tree does not contain the necessary web assets for a complete RetroArch installation. While it supports the regular desktop asset and content downloaders, we also provide a small bundle of UI assets for first launch. You can obtain these files from the nightly Emscripten build on the buildbot, or make them yourself by `zip -r bundle-minimal.zip bundle-minimal` (essentially, just the `assets/ozone`, `assets/pkg`, and `assets/sounds` folders from the regular asset package). + +## Usage + +Hosting the threaded web build is the same as for the multi-threaded emulators above; SSL and proper CORS headers are required. diff --git a/pkg/emscripten/libretro/embed.html b/pkg/emscripten/libretro-thread/embed.html similarity index 100% rename from pkg/emscripten/libretro/embed.html rename to pkg/emscripten/libretro-thread/embed.html diff --git a/pkg/emscripten/libretro-thread/index.html b/pkg/emscripten/libretro-thread/index.html new file mode 100644 index 0000000000..7329c270de --- /dev/null +++ b/pkg/emscripten/libretro-thread/index.html @@ -0,0 +1,203 @@ + + + + + RetroArch Web Player + + + + + + + + + + + + + +
+
+
+ +
+ + RetroArch Logo +
+
+ + + + + + + + + diff --git a/pkg/emscripten/libretro-thread/libretro.css b/pkg/emscripten/libretro-thread/libretro.css new file mode 100644 index 0000000000..be5ad14f30 --- /dev/null +++ b/pkg/emscripten/libretro-thread/libretro.css @@ -0,0 +1,136 @@ +/** + * RetroArch Web Player + * + * This provides the basic styling for the RetroArch web player. + */ + +/** + * Make sure the background of the player is black. + * Also make sure line height is 0 so there's no extra space on the bottom. + */ +.webplayer-container { + background-color: black; + line-height: 0; +} + +/** + * Webplayer Preview when not loaded. + */ +.webplayer-preview { + margin: 0 auto; + cursor: wait; + opacity: 0.2; + transition: all 0.8s; + -webkit-animation: loading 0.8s ease-in-out infinite alternate; + -moz-animation: loading 0.8s ease-in-out infinite alternate; + animation: loading 0.8s ease-in-out infinite alternate; +} +.webplayer-preview.loaded { + cursor: pointer; + opacity: 1; + -webkit-animation: loaded 0.8s ease-in-out; + -moz-animation: loaded 0.8s ease-in-out; + animation: loaded 0.8s ease-in-out; +} +@keyframes loaded { + from { + opacity: 0.2; + } + to { + opacity: 1; + } +} +@-moz-keyframes loaded { + from { + opacity: 0.2; + } + to { + opacity: 1; + } +} +@-webkit-keyframes loaded { + from { + opacity: 0.2; + } + to { + opacity: 1; + } +} +@keyframes loading{ + from { + opacity: 0.2; + } + to { + opacity: 0.35; + } +} +@-moz-keyframes loading{ + from { + opacity: 0.2; + } + to { + opacity: 0.35; + } +} +@-webkit-keyframes loading { + from { + opacity: 0.2; + } + to { + opacity: 0.35; + } +} + +canvas.webplayer { + border: none; + outline: none; + width: 800px; + height: 600px; +} + +#canvas_div { + width: 100%; + height: 100%; + position: absolute; +} + +/** + * Hack to make emscripten stop messing with the canvas size while in fullscreen. + * Foiled again! + */ +:fullscreen canvas.webplayer { + min-width: 100vw; + max-width: 100vw; + min-height: 100vh; + max-height: 100vh; +} + +textarea { + font-family: monospace; + font-size: 0.7em; + height: 95%; + width: 95%; + border-style: none; + border-color: transparent; + overflow: auto; + resize: none; +} + +/** + * Toggle Top Navigation + */ +.toggleMenu { + float: right; +} +.showMenu { + position: absolute; + right: 0; + cursor: pointer; +} +#icnShowMenu { + color: #565656 !important; +} + +.navbar { + box-shadow: none; +} diff --git a/pkg/emscripten/libretro-thread/libretro.js b/pkg/emscripten/libretro-thread/libretro.js new file mode 100644 index 0000000000..8d0a3e8740 --- /dev/null +++ b/pkg/emscripten/libretro-thread/libretro.js @@ -0,0 +1,298 @@ +/** + * RetroArch Web Player + * + * This provides the basic JavaScript for the RetroArch web player. + */ + +var filesystem_ready = false; +var retroarch_ready = false; +var setImmediate; + +var Module = { + noInitialRun: true, + arguments: ["-v", "--menu"], + noImageDecoding: true, + noAudioDecoding: true, + + encoder: new TextEncoder(), + message_queue: [], + message_out: [], + message_accum: "", + + retroArchSend: function(msg) { + let bytes = this.encoder.encode(msg + "\n"); + this.message_queue.push([bytes, 0]); + }, + retroArchRecv: function() { + let out = this.message_out.shift(); + if (out == null && this.message_accum != "") { + out = this.message_accum; + this.message_accum = ""; + } + return out; + }, + preRun: [ + function(module) { + Module.ENV['OPFS'] = "/home/web_user/retroarch"; + }, + function(module) { + function stdin() { + // Return ASCII code of character, or null if no input + while (module.message_queue.length > 0) { + var msg = module.message_queue[0][0]; + var index = module.message_queue[0][1]; + if (index >= msg.length) { + module.message_queue.shift(); + } else { + module.message_queue[0][1] = index + 1; + // assumption: msg is a uint8array + return msg[index]; + } + } + return null; + } + + function stdout(c) { + if (c == null) { + // flush + if (module.message_accum != "") { + module.message_out.push(module.message_accum); + module.message_accum = ""; + } + } else { + let s = String.fromCharCode(c); + if (s == "\n") { + if (module.message_accum != "") { + module.message_out.push(module.message_accum); + module.message_accum = ""; + } + } else { + module.message_accum = module.message_accum + s; + } + } + } + module.FS.init(stdin, stdout); + } + ], + postRun: [], + onRuntimeInitialized: function() { + retroarch_ready = true; + appInitialized(); + }, + print: function(text) { + console.log("stdout:", text); + }, + printErr: function(text) { + console.log("stderr:", text); + }, + canvas: document.getElementById("canvas"), + totalDependencies: 0, + monitorRunDependencies: function(left) { + this.totalDependencies = Math.max(this.totalDependencies, left); + } +}; + + +async function cleanupStorage() +{ + localStorage.clear(); + const root = await navigator.storage.getDirectory(); + for await (const handle of root.values()) { + await root.removeEntry(handle.name, {recursive: true}); + } + document.getElementById("btnClean").disabled = true; +} + +function appInitialized() +{ + /* Need to wait for the wasm runtime to load before enabling the Run button. */ + if (retroarch_ready && filesystem_ready) + { + preLoadingComplete(); + } + } + +function preLoadingComplete() { + $('#icnRun').removeClass('fa-spinner').removeClass('fa-spin'); + $('#icnRun').addClass('fa-play'); + // Make the Preview image clickable to start RetroArch. + $('.webplayer-preview').addClass('loaded').click(function() { + startRetroArch(); + return false; + }); + $('#btnRun').removeClass('disabled').removeAttr("disabled").click(function() { + startRetroArch(); + return false; + }); +} + +// Retrieve the value of the given GET parameter. +function getParam(name) { + var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href); + if (results) { + return results[1] || null; + } +} + +function startRetroArch() { + $('.webplayer').show(); + $('.webplayer-preview').hide(); + document.getElementById("btnRun").disabled = true; + + $('#btnAdd').removeClass("disabled").removeAttr("disabled").click(function() { + $('#btnRom').click(); + }); + $('#btnRom').removeAttr("disabled").change(function(e) { + selectFiles(e.target.files); + }); + $('#btnMenu').removeClass("disabled").removeAttr("disabled").click(function() { + Module._cmd_toggle_menu(); + Module.canvas.focus(); + }); + $('#btnFullscreen').removeClass("disabled").removeAttr("disabled").click(function() { + Module.requestFullscreen(false); + Module.canvas.focus(); + }); + Module.canvas.focus(); + Module.canvas.addEventListener("pointerdown", function() { + Module.canvas.focus(); + }, false); + Module.callMain(Module.arguments); +} + +function selectFiles(files) { + $('#btnAdd').addClass('disabled'); + $('#icnAdd').removeClass('fa-plus'); + $('#icnAdd').addClass('fa-spinner spinning'); + var count = files.length; + + for (var i = 0; i < count; i++) { + filereader = new FileReader(); + filereader.file_name = files[i].name; + filereader.readAsArrayBuffer(files[i]); + filereader.onload = function() { + uploadData(this.result, this.file_name) + }; + filereader.onloadend = function(evt) { + console.log("WEBPLAYER: file: " + this.file_name + " upload complete"); + if (evt.target.readyState == FileReader.DONE) { + $('#btnAdd').removeClass('disabled'); + $('#icnAdd').removeClass('fa-spinner spinning'); + $('#icnAdd').addClass('fa-plus'); + } + } + } +} + +function uploadData(data, name) { + setupWorker.postMessage({command:"upload_file", name:name, data:data}, {transfer:[data]}); +} + +function switchCore(corename) { + localStorage.setItem("core", corename); +} + +function switchStorage(backend) { + if (backend != localStorage.getItem("backend")) { + localStorage.setItem("backend", backend); + location.reload(); + } +} + +// When the browser has loaded everything. +$(function() { + // Enable data clear + $('#btnClean').click(function() { + cleanupStorage(); + }); + + // Enable all available ToolTips. + $('.tooltip-enable').tooltip({ + placement: 'right' + }); + + // Allow hiding the top menu. + $('.showMenu').hide(); + $('#btnHideMenu, .showMenu').click(function() { + $('nav').slideToggle('slow'); + $('.showMenu').toggle('slow'); + }); + + // Attempt to disable some default browser keys. + var keys = { + 9: "tab", + 13: "enter", + 16: "shift", + 18: "alt", + 27: "esc", + 33: "rePag", + 34: "avPag", + 35: "end", + 36: "home", + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 112: "F1", + 113: "F2", + 114: "F3", + 115: "F4", + 116: "F5", + 117: "F6", + 118: "F7", + 119: "F8", + 120: "F9", + 121: "F10", + 122: "F11", + 123: "F12" + }; + window.addEventListener('keydown', function(e) { + if (keys[e.which]) { + e.preventDefault(); + } + }); + + // Switch the core when selecting one. + $('#core-selector a').click(function() { + var coreChoice = $(this).data('core'); + switchCore(coreChoice); + }); + // Find which core to load. + var core = localStorage.getItem("core", core); + if (!core) { + core = 'gambatte'; + } + loadCore(core); +}); + +async function downloadScript(src) { + let resp = await fetch(src); + let blob = await resp.blob(); + return blob; +} + +function loadCore(core) { + // Make the core the selected core in the UI. + var coreTitle = $('#core-selector a[data-core="' + core + '"]').addClass('active').text(); + $('#dropdownMenu1').text(coreTitle); + downloadScript("./"+core+"_libretro.js").then(scriptBlob => { + Module.mainScriptUrlOrBlob = scriptBlob; + import(URL.createObjectURL(scriptBlob)).then(script => { + script.default(Module).then(mod => { + Module = mod; + }).catch(err => { console.error("Couldn't instantiate module",err); throw err; }); + }).catch(err => { console.error("Couldn't load script",err); throw err; }); + }); +} + +const setupWorker = new Worker("libretro.worker.js"); +setupWorker.onmessage = (msg) => { + if(msg.data.command == "loaded_bundle") { + filesystem_ready = true; + localStorage.setItem("asset_time", msg.data.time); + appInitialized(); + } else if(msg.data.command == "uploaded_file") { + // console.log("finished upload of",msg.data.name); + } +} +setupWorker.postMessage({command:"load_bundle",time:localStorage.getItem("asset_time") ?? ""}); diff --git a/pkg/emscripten/libretro-thread/libretro.worker.js b/pkg/emscripten/libretro-thread/libretro.worker.js new file mode 100644 index 0000000000..008fc715ec --- /dev/null +++ b/pkg/emscripten/libretro-thread/libretro.worker.js @@ -0,0 +1,66 @@ +importScripts("zip-no-worker.min.js"); + +async function writeFile(path, data) { + const root = await navigator.storage.getDirectory(); + const dir_end = path.lastIndexOf("/"); + const parent = path.substr(0, dir_end); + const child = path.substr(dir_end+1); + const parent_dir = await mkdirTree(parent); + const file = await parent_dir.getFileHandle(child,{create:true}); + const stream = await file.createSyncAccessHandle(); + const written = stream.write(data); + stream.close(); +} + +async function mkdirTree(path) { + const root = await navigator.storage.getDirectory(); + const parts = path.split("/"); + let here = root; + for (const part of parts) { + if (part == "") { continue; } + here = await here.getDirectoryHandle(part, {create:true}); + } + return here; +} + +async function setupZipFS(zipBuf) { + const root = await navigator.storage.getDirectory(); + const zipReader = new zip.ZipReader(new zip.Uint8ArrayReader(zipBuf), {useWebWorkers:false}); + const entries = await zipReader.getEntries(); + for(const file of entries) { + if (file.getData && !file.directory) { + const writer = new zip.Uint8ArrayWriter(); + const data = await file.getData(writer); + await writeFile(file.filename, data); + } else if (file.directory) { + await mkdirTree(file.filename); + } + } + await zipReader.close(); +} + +onmessage = async (msg) => { + if(msg.data.command == "load_bundle") { + let old_timestamp = msg.data; + try { + const root = await navigator.storage.getDirectory(); + const _bundle = await root.getDirectoryHandle("bundle"); + } catch (_e) { + old_timestamp = ""; + } + let resp = await fetch("assets/frontend/bundle-minimal.zip", { + headers: { + "If-Modified-Since": old_timestamp + } + }); + if (resp.status == 200) { + await setupZipFS(new Uint8Array(await resp.arrayBuffer())); + } else { + await resp.text(); + } + postMessage({command:"loaded_bundle", time:resp.headers.get("last-modified")}); + } else if(msg.data.command == "upload_file") { + await writeFile("/home/web_user/retroarch/userdata/content/"+msg.data.name, new Uint8Array(msg.data.data)); + postMessage({command:"uploaded_file",name:msg.data.name}); + } +} diff --git a/pkg/emscripten/libretro-thread/zip-no-worker.min.js b/pkg/emscripten/libretro-thread/zip-no-worker.min.js new file mode 100644 index 0000000000..cb5596a46f --- /dev/null +++ b/pkg/emscripten/libretro-thread/zip-no-worker.min.js @@ -0,0 +1,2 @@ +((e,t)=>{"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).zip={})})(this,(function(e){"use strict";const{Array:t,Object:n,String:i,Number:r,BigInt:a,Math:s,Date:o,Map:l,Set:c,Response:d,URL:u,Error:f,Uint8Array:_,Uint16Array:w,Uint32Array:p,DataView:h,Blob:b,Promise:g,TextEncoder:m,TextDecoder:x,document:y,crypto:k,btoa:S,TransformStream:z,ReadableStream:v,WritableStream:E,CompressionStream:D,DecompressionStream:F,navigator:R,Worker:T}="undefined"!=typeof globalThis?globalThis:this||self,A=-2;function C(e){return N(e.map((([e,n])=>new t(e).fill(n,0,e))))}function N(e){return e.reduce(((e,n)=>e.concat(t.isArray(n)?N(n):n)),[])}const U=[0,1,2,3].concat(...C([[2,4],[2,5],[4,6],[4,7],[8,8],[8,9],[16,10],[16,11],[32,12],[32,13],[64,14],[64,15],[2,0],[1,16],[1,17],[2,18],[2,19],[4,20],[4,21],[8,22],[8,23],[16,24],[16,25],[32,26],[32,27],[64,28],[64,29]]));function W(){const e=this;function t(e,t){let n=0;do{n|=1&e,e>>>=1,n<<=1}while(--t>0);return n>>>1}e.build_tree=n=>{const i=e.dyn_tree,r=e.stat_desc.static_tree,a=e.stat_desc.elems;let o,l,c,d=-1;for(n.heap_len=0,n.heap_max=573,o=0;a>o;o++)0!==i[2*o]?(n.heap[++n.heap_len]=d=o,n.depth[o]=0):i[2*o+1]=0;for(;2>n.heap_len;)c=n.heap[++n.heap_len]=2>d?++d:0,i[2*c]=1,n.depth[c]=0,n.opt_len--,r&&(n.static_len-=r[2*c+1]);for(e.max_code=d,o=s.floor(n.heap_len/2);o>=1;o--)n.pqdownheap(i,o);c=a;do{o=n.heap[1],n.heap[1]=n.heap[n.heap_len--],n.pqdownheap(i,1),l=n.heap[1],n.heap[--n.heap_max]=o,n.heap[--n.heap_max]=l,i[2*c]=i[2*o]+i[2*l],n.depth[c]=s.max(n.depth[o],n.depth[l])+1,i[2*o+1]=i[2*l+1]=c,n.heap[1]=c++,n.pqdownheap(i,1)}while(n.heap_len>=2);n.heap[--n.heap_max]=n.heap[1],(t=>{const n=e.dyn_tree,i=e.stat_desc.static_tree,r=e.stat_desc.extra_bits,a=e.stat_desc.extra_base,s=e.stat_desc.max_length;let o,l,c,d,u,f,_=0;for(d=0;15>=d;d++)t.bl_count[d]=0;for(n[2*t.heap[t.heap_max]+1]=0,o=t.heap_max+1;573>o;o++)l=t.heap[o],d=n[2*n[2*l+1]+1]+1,d>s&&(d=s,_++),n[2*l+1]=d,l>e.max_code||(t.bl_count[d]++,u=0,a>l||(u=r[l-a]),f=n[2*l],t.opt_len+=f*(d+u),i&&(t.static_len+=f*(i[2*l+1]+u)));if(0!==_){do{for(d=s-1;0===t.bl_count[d];)d--;t.bl_count[d]--,t.bl_count[d+1]+=2,t.bl_count[s]--,_-=2}while(_>0);for(d=s;0!==d;d--)for(l=t.bl_count[d];0!==l;)c=t.heap[--o],c>e.max_code||(n[2*c+1]!=d&&(t.opt_len+=(d-n[2*c+1])*n[2*c],n[2*c+1]=d),l--)}})(n),((e,n,i)=>{const r=[];let a,s,o,l=0;for(a=1;15>=a;a++)r[a]=l=l+i[a-1]<<1;for(s=0;n>=s;s++)o=e[2*s+1],0!==o&&(e[2*s]=t(r[o]++,o))})(i,e.max_code,n.bl_count)}}function I(e,t,n,i,r){const a=this;a.static_tree=e,a.extra_bits=t,a.extra_base=n,a.elems=i,a.max_length=r}W._length_code=[0,1,2,3,4,5,6,7].concat(...C([[2,8],[2,9],[2,10],[2,11],[4,12],[4,13],[4,14],[4,15],[8,16],[8,17],[8,18],[8,19],[16,20],[16,21],[16,22],[16,23],[32,24],[32,25],[32,26],[31,27],[1,28]])),W.base_length=[0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224,0],W.base_dist=[0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576],W.d_code=e=>256>e?U[e]:U[256+(e>>>7)],W.extra_lbits=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],W.extra_dbits=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],W.extra_blbits=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],W.bl_order=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];const L=C([[144,8],[112,9],[24,7],[8,8]]);I.static_ltree=N([12,140,76,204,44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,237,29,157,93,221,61,189,125,253,19,275,147,403,83,339,211,467,51,307,179,435,115,371,243,499,11,267,139,395,75,331,203,459,43,299,171,427,107,363,235,491,27,283,155,411,91,347,219,475,59,315,187,443,123,379,251,507,7,263,135,391,71,327,199,455,39,295,167,423,103,359,231,487,23,279,151,407,87,343,215,471,55,311,183,439,119,375,247,503,15,271,143,399,79,335,207,463,47,303,175,431,111,367,239,495,31,287,159,415,95,351,223,479,63,319,191,447,127,383,255,511,0,64,32,96,16,80,48,112,8,72,40,104,24,88,56,120,4,68,36,100,20,84,52,116,3,131,67,195,35,163,99,227].map(((e,t)=>[e,L[t]])));const O=C([[30,5]]);function M(e,t,n,i,r){const a=this;a.good_length=e,a.max_lazy=t,a.nice_length=n,a.max_chain=i,a.func=r}I.static_dtree=N([0,16,8,24,4,20,12,28,2,18,10,26,6,22,14,30,1,17,9,25,5,21,13,29,3,19,11,27,7,23].map(((e,t)=>[e,O[t]]))),I.static_l_desc=new I(I.static_ltree,W.extra_lbits,257,286,15),I.static_d_desc=new I(I.static_dtree,W.extra_dbits,0,30,15),I.static_bl_desc=new I(null,W.extra_blbits,0,19,7);const P=[new M(0,0,0,0,0),new M(4,4,8,4,1),new M(4,5,16,8,1),new M(4,6,32,32,1),new M(4,4,16,16,2),new M(8,16,32,32,2),new M(8,16,128,128,2),new M(8,32,128,256,2),new M(32,128,258,1024,2),new M(32,258,258,4096,2)],H=["need dictionary","stream end","","","stream error","data error","","buffer error","",""],B=113,q=666,V=262;function Z(e,t,n,i){const r=e[2*t],a=e[2*n];return a>r||r==a&&i[t]<=i[n]}function K(){const e=this;let t,n,i,r,a,o,l,c,d,u,f,p,h,b,g,m,x,y,k,S,z,v,E,D,F,R,T,C,N,U,L,O,M;const K=new W,Y=new W,G=new W;let X,j,J,Q,$,ee;function te(){let t;for(t=0;286>t;t++)L[2*t]=0;for(t=0;30>t;t++)O[2*t]=0;for(t=0;19>t;t++)M[2*t]=0;L[512]=1,e.opt_len=e.static_len=0,j=J=0}function ne(e,t){let n,i=-1,r=e[1],a=0,s=7,o=4;0===r&&(s=138,o=3),e[2*(t+1)+1]=65535;for(let l=0;t>=l;l++)n=r,r=e[2*(l+1)+1],++aa?M[2*n]+=a:0!==n?(n!=i&&M[2*n]++,M[32]++):a>10?M[36]++:M[34]++,a=0,i=n,0===r?(s=138,o=3):n==r?(s=6,o=3):(s=7,o=4))}function ie(t){e.pending_buf[e.pending++]=t}function re(e){ie(255&e),ie(e>>>8&255)}function ae(e,t){let n;const i=t;ee>16-i?(n=e,$|=n<>>16-ee,ee+=i-16):($|=e<=n;n++)if(i=a,a=e[2*(n+1)+1],++s>=o||i!=a){if(l>s)do{se(i,M)}while(0!=--s);else 0!==i?(i!=r&&(se(i,M),s--),se(16,M),ae(s-3,2)):s>10?(se(18,M),ae(s-11,7)):(se(17,M),ae(s-3,3));s=0,r=i,0===a?(o=138,l=3):i==a?(o=6,l=3):(o=7,l=4)}}function le(){16==ee?(re($),$=0,ee=0):8>ee||(ie(255&$),$>>>=8,ee-=8)}function ce(t,n){let i,r,a;if(e.dist_buf[j]=t,e.lc_buf[j]=255&n,j++,0===t?L[2*n]++:(J++,t--,L[2*(W._length_code[n]+256+1)]++,O[2*W.d_code(t)]++),!(8191&j)&&T>2){for(i=8*j,r=z-x,a=0;30>a;a++)i+=O[2*a]*(5+W.extra_dbits[a]);if(i>>>=3,Jo);se(256,t),Q=t[513]}function ue(){ee>8?re($):ee>0&&ie(255&$),$=0,ee=0}function fe(t,n,i){ae(0+(i?1:0),3),((t,n)=>{ue(),Q=8,re(n),re(~n),e.pending_buf.set(c.subarray(t,t+n),e.pending),e.pending+=n})(t,n)}function _e(n){((t,n,i)=>{let r,a,s=0;T>0?(K.build_tree(e),Y.build_tree(e),s=(()=>{let t;for(ne(L,K.max_code),ne(O,Y.max_code),G.build_tree(e),t=18;t>=3&&0===M[2*W.bl_order[t]+1];t--);return e.opt_len+=3*(t+1)+5+5+4,t})(),r=e.opt_len+3+7>>>3,a=e.static_len+3+7>>>3,a>r||(r=a)):r=a=n+5,n+4>r||-1==t?a==r?(ae(2+(i?1:0),3),de(I.static_ltree,I.static_dtree)):(ae(4+(i?1:0),3),((e,t,n)=>{let i;for(ae(e-257,5),ae(t-1,5),ae(n-4,4),i=0;n>i;i++)ae(M[2*W.bl_order[i]+1],3);oe(L,e-1),oe(O,t-1)})(K.max_code+1,Y.max_code+1,s+1),de(L,O)):fe(t,n,i),te(),i&&ue()})(0>x?-1:x,z-x,n),x=z,t.flush_pending()}function we(){let e,n,i,r;do{if(r=d-E-z,0===r&&0===z&&0===E)r=a;else if(-1==r)r--;else if(z>=a+a-V){c.set(c.subarray(a,a+a),0),v-=a,z-=a,x-=a,e=h,i=e;do{n=65535&f[--i],f[i]=a>n?0:n-a}while(0!=--e);e=a,i=e;do{n=65535&u[--i],u[i]=a>n?0:n-a}while(0!=--e);r+=a}if(0===t.avail_in)return;e=t.read_buf(c,z+E,r),E+=e,3>E||(p=255&c[z],p=(p<E&&0!==t.avail_in)}function pe(e){let t,n,i=F,r=z,s=D;const o=z>a-V?z-(a-V):0;let d=U;const f=l,_=z+258;let w=c[r+s-1],p=c[r+s];N>D||(i>>=2),d>E&&(d=E);do{if(t=e,c[t+s]==p&&c[t+s-1]==w&&c[t]==c[r]&&c[++t]==c[r+1]){r+=2,t++;do{}while(c[++r]==c[++t]&&c[++r]==c[++t]&&c[++r]==c[++t]&&c[++r]==c[++t]&&c[++r]==c[++t]&&c[++r]==c[++t]&&c[++r]==c[++t]&&c[++r]==c[++t]&&_>r);if(n=258-(_-r),r=_-258,n>s){if(v=e,s=n,n>=d)break;w=c[r+s-1],p=c[r+s]}}}while((e=65535&u[e&f])>o&&0!=--i);return s>E?E:s}e.depth=[],e.bl_count=[],e.heap=[],L=[],O=[],M=[],e.pqdownheap=(t,n)=>{const i=e.heap,r=i[n];let a=n<<1;for(;a<=e.heap_len&&(a(W||(W=8),H||(H=8),q||(q=0),t.msg=null,-1==k&&(k=6),1>H||H>9||8!=W||9>v||v>15||0>k||k>9||0>q||q>2?A:(t.dstate=e,o=v,a=1<(t.total_in=t.total_out=0,t.msg=null,e.pending=0,e.pending_out=0,n=B,r=0,K.dyn_tree=L,K.stat_desc=I.static_l_desc,Y.dyn_tree=O,Y.stat_desc=I.static_d_desc,G.dyn_tree=M,G.stat_desc=I.static_bl_desc,$=0,ee=0,Q=8,te(),(()=>{d=2*a,f[h-1]=0;for(let e=0;h-1>e;e++)f[e]=0;R=P[T].max_lazy,N=P[T].good_length,U=P[T].nice_length,F=P[T].max_chain,z=0,x=0,E=0,y=D=2,S=0,p=0})(),0))(t))),e.deflateEnd=()=>42!=n&&n!=B&&n!=q?A:(e.lc_buf=null,e.dist_buf=null,e.pending_buf=null,f=null,u=null,c=null,e.dstate=null,n==B?-3:0),e.deflateParams=(e,t,n)=>{let i=0;return-1==t&&(t=6),0>t||t>9||0>n||n>2?A:(P[T].func!=P[t].func&&0!==e.total_in&&(i=e.deflate(1)),T!=t&&(T=t,R=P[T].max_lazy,N=P[T].good_length,U=P[T].nice_length,F=P[T].max_chain),C=n,i)},e.deflateSetDictionary=(e,t,i)=>{let r,s=i,o=0;if(!t||42!=n)return A;if(3>s)return 0;for(s>a-V&&(s=a-V,o=i-s),c.set(t.subarray(o,o+s),0),z=s,x=s,p=255&c[0],p=(p<=r;r++)p=(p<{let _,w,b,F,N;if(d>4||0>d)return A;if(!s.next_out||!s.next_in&&0!==s.avail_in||n==q&&4!=d)return s.msg=H[4],A;if(0===s.avail_out)return s.msg=H[7],-5;var U;if(t=s,F=r,r=d,42==n&&(w=8+(o-8<<4)<<8,b=(T-1&255)>>1,b>3&&(b=3),w|=b<<6,0!==z&&(w|=32),w+=31-w%31,n=B,ie((U=w)>>8&255),ie(255&U)),0!==e.pending){if(t.flush_pending(),0===t.avail_out)return r=-1,0}else if(0===t.avail_in&&F>=d&&4!=d)return t.msg=H[7],-5;if(n==q&&0!==t.avail_in)return s.msg=H[7],-5;if(0!==t.avail_in||0!==E||0!=d&&n!=q){switch(N=-1,P[T].func){case 0:N=(e=>{let n,r=65535;for(r>i-5&&(r=i-5);;){if(1>=E){if(we(),0===E&&0==e)return 0;if(0===E)break}if(z+=E,E=0,n=x+r,(0===z||z>=n)&&(E=z-n,z=n,_e(!1),0===t.avail_out))return 0;if(z-x>=a-V&&(_e(!1),0===t.avail_out))return 0}return _e(4==e),0===t.avail_out?4==e?2:0:4==e?3:1})(d);break;case 1:N=(e=>{let n,i=0;for(;;){if(V>E){if(we(),V>E&&0==e)return 0;if(0===E)break}if(3>E||(p=(p<a-V||2!=C&&(y=pe(i)),3>y)n=ce(0,255&c[z]),E--,z++;else if(n=ce(z-v,y-3),E-=y,y>R||3>E)z+=y,y=0,p=255&c[z],p=(p<{let n,i,r=0;for(;;){if(V>E){if(we(),V>E&&0==e)return 0;if(0===E)break}if(3>E||(p=(p<D&&a-V>=(z-r&65535)&&(2!=C&&(y=pe(r)),5>=y&&(1==C||3==y&&z-v>4096)&&(y=2)),3>D||y>D)if(0!==S){if(n=ce(0,255&c[z-1]),n&&_e(!1),z++,E--,0===t.avail_out)return 0}else S=1,z++,E--;else{i=z+E-3,n=ce(z-1-k,D-3),E-=D-1,D-=2;do{++z>i||(p=(p<1+Q+10-ee&&(ae(2,3),se(256,I.static_ltree),le()),Q=7;else if(fe(0,0,!1),3==d)for(_=0;h>_;_++)f[_]=0;if(t.flush_pending(),0===t.avail_out)return r=-1,0}}return 4!=d?0:1}}function Y(){const e=this;e.next_in_index=0,e.next_out_index=0,e.avail_in=0,e.total_in=0,e.avail_out=0,e.total_out=0}Y.prototype={deflateInit(e,t){const n=this;return n.dstate=new K,t||(t=15),n.dstate.deflateInit(n,e,t)},deflate(e){const t=this;return t.dstate?t.dstate.deflate(t,e):A},deflateEnd(){const e=this;if(!e.dstate)return A;const t=e.dstate.deflateEnd();return e.dstate=null,t},deflateParams(e,t){const n=this;return n.dstate?n.dstate.deflateParams(n,e,t):A},deflateSetDictionary(e,t){const n=this;return n.dstate?n.dstate.deflateSetDictionary(n,e,t):A},read_buf(e,t,n){const i=this;let r=i.avail_in;return r>n&&(r=n),0===r?0:(i.avail_in-=r,e.set(i.next_in.subarray(i.next_in_index,i.next_in_index+r),t),i.next_in_index+=r,i.total_in+=r,r)},flush_pending(){const e=this;let t=e.dstate.pending;t>e.avail_out&&(t=e.avail_out),0!==t&&(e.next_out.set(e.dstate.pending_buf.subarray(e.dstate.pending_out,e.dstate.pending_out+t),e.next_out_index),e.next_out_index+=t,e.dstate.pending_out+=t,e.total_out+=t,e.avail_out-=t,e.dstate.pending-=t,0===e.dstate.pending&&(e.dstate.pending_out=0))}};const G=-2,X=-3,j=-5,J=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],Q=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],$=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],ee=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],te=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],ne=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],ie=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];function re(){let e,t,n,i,r,a;function s(e,t,s,o,l,c,d,u,f,_,w){let p,h,b,g,m,x,y,k,S,z,v,E,D,F,R;z=0,m=s;do{n[e[t+z]]++,z++,m--}while(0!==m);if(n[0]==s)return d[0]=-1,u[0]=0,0;for(k=u[0],x=1;15>=x&&0===n[x];x++);for(y=x,x>k&&(k=x),m=15;0!==m&&0===n[m];m--);for(b=m,k>m&&(k=m),u[0]=k,F=1<x;x++,F<<=1)if(0>(F-=n[x]))return X;if(0>(F-=n[m]))return X;for(n[m]+=F,a[1]=x=0,z=1,D=2;0!=--m;)a[D]=x+=n[z],D++,z++;m=0,z=0;do{0!==(x=e[t+z])&&(w[a[x]++]=m),z++}while(++m=y;y++)for(p=n[y];0!=p--;){for(;y>E+k;){if(g++,E+=k,R=b-E,R=R>k?k:R,(h=1<<(x=y-E))>p+1&&(h-=p+1,D=y,R>x))for(;++xn[++D];)h-=n[D];if(R=1<1440)return X;r[g]=v=_[0],_[0]+=R,0!==g?(a[g]=m,i[0]=x,i[1]=k,x=m>>>E-k,i[2]=v-r[g-1]-x,f.set(i,3*(r[g-1]+x))):d[0]=v}for(i[1]=y-E,s>z?w[z]w[z]?0:96,i[2]=w[z++]):(i[0]=c[w[z]-o]+16+64,i[2]=l[w[z++]-o]):i[0]=192,h=1<>>E;R>x;x+=h)f.set(i,3*(v+x));for(x=1<>>=1)m^=x;for(m^=x,S=(1<o;o++)t[o]=0;for(o=0;16>o;o++)n[o]=0;for(o=0;3>o;o++)i[o]=0;r.set(n.subarray(0,15),0),a.set(n.subarray(0,16),0)}this.inflate_trees_bits=(n,i,r,a,l)=>{let c;return o(19),e[0]=0,c=s(n,0,19,19,null,null,r,i,a,e,t),c==X?l.msg="oversubscribed dynamic bit lengths tree":c!=j&&0!==i[0]||(l.msg="incomplete dynamic bit lengths tree",c=X),c},this.inflate_trees_dynamic=(n,i,r,a,l,c,d,u,f)=>{let _;return o(288),e[0]=0,_=s(r,0,n,257,ee,te,c,a,u,e,t),0!=_||0===a[0]?(_==X?f.msg="oversubscribed literal/length tree":-4!=_&&(f.msg="incomplete literal/length tree",_=X),_):(o(288),_=s(r,n,i,0,ne,ie,d,l,u,e,t),0!=_||0===l[0]&&n>257?(_==X?f.msg="oversubscribed distance tree":_==j?(f.msg="incomplete distance tree",_=X):-4!=_&&(f.msg="empty distance tree with lengths",_=X),_):0)}}function ae(){const e=this;let t,n,i,r,a=0,s=0,o=0,l=0,c=0,d=0,u=0,f=0,_=0,w=0;function p(e,t,n,i,r,a,s,o){let l,c,d,u,f,_,w,p,h,b,g,m,x,y,k,S;w=o.next_in_index,p=o.avail_in,f=s.bitb,_=s.bitk,h=s.write,b=h_;)p--,f|=(255&o.read_byte(w++))<<_,_+=8;if(l=f&g,c=n,d=i,S=3*(d+l),0!==(u=c[S]))for(;;){if(f>>=c[S+1],_-=c[S+1],16&u){for(u&=15,x=c[S+2]+(f&J[u]),f>>=u,_-=u;15>_;)p--,f|=(255&o.read_byte(w++))<<_,_+=8;for(l=f&m,c=r,d=a,S=3*(d+l),u=c[S];;){if(f>>=c[S+1],_-=c[S+1],16&u){for(u&=15;u>_;)p--,f|=(255&o.read_byte(w++))<<_,_+=8;if(y=c[S+2]+(f&J[u]),f>>=u,_-=u,b-=x,y>h){k=h-y;do{k+=s.end}while(0>k);if(u=s.end-k,x>u){if(x-=u,h-k>0&&u>h-k)do{s.win[h++]=s.win[k++]}while(0!=--u);else s.win.set(s.win.subarray(k,k+u),h),h+=u,k+=u,u=0;k=0}}else k=h-y,h-k>0&&2>h-k?(s.win[h++]=s.win[k++],s.win[h++]=s.win[k++],x-=2):(s.win.set(s.win.subarray(k,k+2),h),h+=2,k+=2,x-=2);if(h-k>0&&x>h-k)do{s.win[h++]=s.win[k++]}while(0!=--x);else s.win.set(s.win.subarray(k,k+x),h),h+=x,k+=x,x=0;break}if(64&u)return o.msg="invalid distance code",x=o.avail_in-p,x=x>_>>3?_>>3:x,p+=x,w-=x,_-=x<<3,s.bitb=f,s.bitk=_,o.avail_in=p,o.total_in+=w-o.next_in_index,o.next_in_index=w,s.write=h,X;l+=c[S+2],l+=f&J[u],S=3*(d+l),u=c[S]}break}if(64&u)return 32&u?(x=o.avail_in-p,x=x>_>>3?_>>3:x,p+=x,w-=x,_-=x<<3,s.bitb=f,s.bitk=_,o.avail_in=p,o.total_in+=w-o.next_in_index,o.next_in_index=w,s.write=h,1):(o.msg="invalid literal/length code",x=o.avail_in-p,x=x>_>>3?_>>3:x,p+=x,w-=x,_-=x<<3,s.bitb=f,s.bitk=_,o.avail_in=p,o.total_in+=w-o.next_in_index,o.next_in_index=w,s.write=h,X);if(l+=c[S+2],l+=f&J[u],S=3*(d+l),0===(u=c[S])){f>>=c[S+1],_-=c[S+1],s.win[h++]=c[S+2],b--;break}}else f>>=c[S+1],_-=c[S+1],s.win[h++]=c[S+2],b--}while(b>=258&&p>=10);return x=o.avail_in-p,x=x>_>>3?_>>3:x,p+=x,w-=x,_-=x<<3,s.bitb=f,s.bitk=_,o.avail_in=p,o.total_in+=w-o.next_in_index,o.next_in_index=w,s.write=h,0}e.init=(e,a,s,o,l,c)=>{t=0,u=e,f=a,i=s,_=o,r=l,w=c,n=null},e.proc=(e,h,b)=>{let g,m,x,y,k,S,z,v=0,E=0,D=0;for(D=h.next_in_index,y=h.avail_in,v=e.bitb,E=e.bitk,k=e.write,S=k=258&&y>=10&&(e.bitb=v,e.bitk=E,h.avail_in=y,h.total_in+=D-h.next_in_index,h.next_in_index=D,e.write=k,b=p(u,f,i,_,r,w,e,h),D=h.next_in_index,y=h.avail_in,v=e.bitb,E=e.bitk,k=e.write,S=kE;){if(0===y)return e.bitb=v,e.bitk=E,h.avail_in=y,h.total_in+=D-h.next_in_index,h.next_in_index=D,e.write=k,e.inflate_flush(h,b);b=0,y--,v|=(255&h.read_byte(D++))<>>=n[m+1],E-=n[m+1],x=n[m],0===x){l=n[m+2],t=6;break}if(16&x){c=15&x,a=n[m+2],t=2;break}if(!(64&x)){o=x,s=m/3+n[m+2];break}if(32&x){t=7;break}return t=9,h.msg="invalid literal/length code",b=X,e.bitb=v,e.bitk=E,h.avail_in=y,h.total_in+=D-h.next_in_index,h.next_in_index=D,e.write=k,e.inflate_flush(h,b);case 2:for(g=c;g>E;){if(0===y)return e.bitb=v,e.bitk=E,h.avail_in=y,h.total_in+=D-h.next_in_index,h.next_in_index=D,e.write=k,e.inflate_flush(h,b);b=0,y--,v|=(255&h.read_byte(D++))<>=g,E-=g,o=f,n=r,s=w,t=3;case 3:for(g=o;g>E;){if(0===y)return e.bitb=v,e.bitk=E,h.avail_in=y,h.total_in+=D-h.next_in_index,h.next_in_index=D,e.write=k,e.inflate_flush(h,b);b=0,y--,v|=(255&h.read_byte(D++))<>=n[m+1],E-=n[m+1],x=n[m],16&x){c=15&x,d=n[m+2],t=4;break}if(!(64&x)){o=x,s=m/3+n[m+2];break}return t=9,h.msg="invalid distance code",b=X,e.bitb=v,e.bitk=E,h.avail_in=y,h.total_in+=D-h.next_in_index,h.next_in_index=D,e.write=k,e.inflate_flush(h,b);case 4:for(g=c;g>E;){if(0===y)return e.bitb=v,e.bitk=E,h.avail_in=y,h.total_in+=D-h.next_in_index,h.next_in_index=D,e.write=k,e.inflate_flush(h,b);b=0,y--,v|=(255&h.read_byte(D++))<>=g,E-=g,t=5;case 5:for(z=k-d;0>z;)z+=e.end;for(;0!==a;){if(0===S&&(k==e.end&&0!==e.read&&(k=0,S=k7&&(E-=8,y++,D--),e.write=k,b=e.inflate_flush(h,b),k=e.write,S=k{}}re.inflate_trees_fixed=(e,t,n,i)=>(e[0]=9,t[0]=5,n[0]=Q,i[0]=$,0);const se=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];function oe(e,t){const n=this;let i,r=0,a=0,s=0,o=0;const l=[0],c=[0],d=new ae;let u=0,f=new Int32Array(4320);const w=new re;n.bitk=0,n.bitb=0,n.win=new _(t),n.end=t,n.read=0,n.write=0,n.reset=(e,t)=>{t&&(t[0]=0),6==r&&d.free(e),r=0,n.bitk=0,n.bitb=0,n.read=n.write=0},n.reset(e,null),n.inflate_flush=(e,t)=>{let i,r,a;return r=e.next_out_index,a=n.read,i=(a>n.write?n.end:n.write)-a,i>e.avail_out&&(i=e.avail_out),0!==i&&t==j&&(t=0),e.avail_out-=i,e.total_out+=i,e.next_out.set(n.win.subarray(a,a+i),r),r+=i,a+=i,a==n.end&&(a=0,n.write==n.end&&(n.write=0),i=n.write-a,i>e.avail_out&&(i=e.avail_out),0!==i&&t==j&&(t=0),e.avail_out-=i,e.total_out+=i,e.next_out.set(n.win.subarray(a,a+i),r),r+=i,a+=i),e.next_out_index=r,n.read=a,t},n.proc=(e,t)=>{let _,p,h,b,g,m,x,y;for(b=e.next_in_index,g=e.avail_in,p=n.bitb,h=n.bitk,m=n.write,x=mh;){if(0===g)return n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);t=0,g--,p|=(255&e.read_byte(b++))<>>1){case 0:p>>>=3,h-=3,_=7&h,p>>>=_,h-=_,r=1;break;case 1:k=[],S=[],z=[[]],v=[[]],re.inflate_trees_fixed(k,S,z,v),d.init(k[0],S[0],z[0],0,v[0],0),p>>>=3,h-=3,r=6;break;case 2:p>>>=3,h-=3,r=3;break;case 3:return p>>>=3,h-=3,r=9,e.msg="invalid block type",t=X,n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t)}break;case 1:for(;32>h;){if(0===g)return n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);t=0,g--,p|=(255&e.read_byte(b++))<>>16&65535)!=(65535&p))return r=9,e.msg="invalid stored block lengths",t=X,n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);a=65535&p,p=h=0,r=0!==a?2:0!==u?7:0;break;case 2:if(0===g)return n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);if(0===x&&(m==n.end&&0!==n.read&&(m=0,x=mg&&(_=g),_>x&&(_=x),n.win.set(e.read_buf(b,_),m),b+=_,g-=_,m+=_,x-=_,0!=(a-=_))break;r=0!==u?7:0;break;case 3:for(;14>h;){if(0===g)return n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);t=0,g--,p|=(255&e.read_byte(b++))<29||(_>>5&31)>29)return r=9,e.msg="too many length or distance symbols",t=X,n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);if(_=258+(31&_)+(_>>5&31),!i||i.length<_)i=[];else for(y=0;_>y;y++)i[y]=0;p>>>=14,h-=14,o=0,r=4;case 4:for(;4+(s>>>10)>o;){for(;3>h;){if(0===g)return n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);t=0,g--,p|=(255&e.read_byte(b++))<>>=3,h-=3}for(;19>o;)i[se[o++]]=0;if(l[0]=7,_=w.inflate_trees_bits(i,l,c,f,e),0!=_)return(t=_)==X&&(i=null,r=9),n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);o=0,r=5;case 5:for(;_=s,258+(31&_)+(_>>5&31)>o;){let a,d;for(_=l[0];_>h;){if(0===g)return n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);t=0,g--,p|=(255&e.read_byte(b++))<d)p>>>=_,h-=_,i[o++]=d;else{for(y=18==d?7:d-14,a=18==d?11:3;_+y>h;){if(0===g)return n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);t=0,g--,p|=(255&e.read_byte(b++))<>>=_,h-=_,a+=p&J[y],p>>>=y,h-=y,y=o,_=s,y+a>258+(31&_)+(_>>5&31)||16==d&&1>y)return i=null,r=9,e.msg="invalid bit length repeat",t=X,n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);d=16==d?i[y-1]:0;do{i[y++]=d}while(0!=--a);o=y}}if(c[0]=-1,E=[],D=[],F=[],R=[],E[0]=9,D[0]=6,_=s,_=w.inflate_trees_dynamic(257+(31&_),1+(_>>5&31),i,E,D,F,R,f,e),0!=_)return _==X&&(i=null,r=9),t=_,n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,n.inflate_flush(e,t);d.init(E[0],D[0],f,F[0],f,R[0]),r=6;case 6:if(n.bitb=p,n.bitk=h,e.avail_in=g,e.total_in+=b-e.next_in_index,e.next_in_index=b,n.write=m,1!=(t=d.proc(n,e,t)))return n.inflate_flush(e,t);if(t=0,d.free(e),b=e.next_in_index,g=e.avail_in,p=n.bitb,h=n.bitk,m=n.write,x=m{n.reset(e,null),n.win=null,f=null},n.set_dictionary=(e,t,i)=>{n.win.set(e.subarray(t,t+i),0),n.read=n.write=i},n.sync_point=()=>1==r?1:0}const le=13,ce=[0,0,255,255];function de(){const e=this;function t(e){return e&&e.istate?(e.total_in=e.total_out=0,e.msg=null,e.istate.mode=7,e.istate.blocks.reset(e,null),0):G}e.mode=0,e.method=0,e.was=[0],e.need=0,e.marker=0,e.wbits=0,e.inflateEnd=t=>(e.blocks&&e.blocks.free(t),e.blocks=null,0),e.inflateInit=(n,i)=>(n.msg=null,e.blocks=null,8>i||i>15?(e.inflateEnd(n),G):(e.wbits=i,n.istate.blocks=new oe(n,1<{let n,i;if(!e||!e.istate||!e.next_in)return G;const r=e.istate;for(t=4==t?j:0,n=j;;)switch(r.mode){case 0:if(0===e.avail_in)return n;if(n=t,e.avail_in--,e.total_in++,8!=(15&(r.method=e.read_byte(e.next_in_index++)))){r.mode=le,e.msg="unknown compression method",r.marker=5;break}if(8+(r.method>>4)>r.wbits){r.mode=le,e.msg="invalid win size",r.marker=5;break}r.mode=1;case 1:if(0===e.avail_in)return n;if(n=t,e.avail_in--,e.total_in++,i=255&e.read_byte(e.next_in_index++),((r.method<<8)+i)%31!=0){r.mode=le,e.msg="incorrect header check",r.marker=5;break}if(!(32&i)){r.mode=7;break}r.mode=2;case 2:if(0===e.avail_in)return n;n=t,e.avail_in--,e.total_in++,r.need=(255&e.read_byte(e.next_in_index++))<<24&4278190080,r.mode=3;case 3:if(0===e.avail_in)return n;n=t,e.avail_in--,e.total_in++,r.need+=(255&e.read_byte(e.next_in_index++))<<16&16711680,r.mode=4;case 4:if(0===e.avail_in)return n;n=t,e.avail_in--,e.total_in++,r.need+=(255&e.read_byte(e.next_in_index++))<<8&65280,r.mode=5;case 5:return 0===e.avail_in?n:(n=t,e.avail_in--,e.total_in++,r.need+=255&e.read_byte(e.next_in_index++),r.mode=6,2);case 6:return r.mode=le,e.msg="need dictionary",r.marker=0,G;case 7:if(n=r.blocks.proc(e,n),n==X){r.mode=le,r.marker=0;break}if(0==n&&(n=t),1!=n)return n;n=t,r.blocks.reset(e,r.was),r.mode=12;case 12:return e.avail_in=0,1;case le:return X;default:return G}},e.inflateSetDictionary=(e,t,n)=>{let i=0,r=n;if(!e||!e.istate||6!=e.istate.mode)return G;const a=e.istate;return r<1<{let n,i,r,a,s;if(!e||!e.istate)return G;const o=e.istate;if(o.mode!=le&&(o.mode=le,o.marker=0),0===(n=e.avail_in))return j;for(i=e.next_in_index,r=o.marker;0!==n&&4>r;)e.read_byte(i)==ce[r]?r++:r=0!==e.read_byte(i)?0:4-r,i++,n--;return e.total_in+=i-e.next_in_index,e.next_in_index=i,e.avail_in=n,o.marker=r,4!=r?X:(a=e.total_in,s=e.total_out,t(e),e.total_in=a,e.total_out=s,o.mode=7,0)},e.inflateSyncPoint=e=>e&&e.istate&&e.istate.blocks?e.istate.blocks.sync_point():G}function ue(){}ue.prototype={inflateInit(e){const t=this;return t.istate=new de,e||(e=15),t.istate.inflateInit(t,e)},inflate(e){const t=this;return t.istate?t.istate.inflate(t,e):G},inflateEnd(){const e=this;if(!e.istate)return G;const t=e.istate.inflateEnd(e);return e.istate=null,t},inflateSync(){const e=this;return e.istate?e.istate.inflateSync(e):G},inflateSetDictionary(e,t){const n=this;return n.istate?n.istate.inflateSetDictionary(n,e,t):G},read_byte(e){return this.next_in[e]},read_buf(e,t){return this.next_in.subarray(e,e+t)}};const fe=4294967295,_e=65535,we=67324752,pe=134695760,he=pe,be=33639248,ge=101010256,me=101075792,xe=117853008,ye=22,ke=21589,Se=2048,ze="/",ve=new o(2107,11,31),Ee=new o(1980,0,1),De=void 0,Fe="undefined",Re="function";class Te{constructor(e){return class extends z{constructor(t,n){const i=new e(n);super({transform(e,t){t.enqueue(i.append(e))},flush(e){const t=i.flush();t&&e.enqueue(t)}})}}}}let Ae=2;try{typeof R!=Fe&&R.hardwareConcurrency&&(Ae=R.hardwareConcurrency)}catch(e){}const Ce={chunkSize:524288,maxWorkers:Ae,terminateWorkerTimeout:5e3,useWebWorkers:!0,useCompressionStream:!0,workerScripts:De,CompressionStreamNative:typeof D!=Fe&&D,DecompressionStreamNative:typeof F!=Fe&&F},Ne=n.assign({},Ce);function Ue(){return Ne}function We(e){return s.max(e.chunkSize,64)}function Ie(e){const{baseURL:n,chunkSize:i,maxWorkers:r,terminateWorkerTimeout:a,useCompressionStream:s,useWebWorkers:o,Deflate:l,Inflate:c,CompressionStream:d,DecompressionStream:u,workerScripts:_}=e;if(Le("baseURL",n),Le("chunkSize",i),Le("maxWorkers",r),Le("terminateWorkerTimeout",a),Le("useCompressionStream",s),Le("useWebWorkers",o),l&&(Ne.CompressionStream=new Te(l)),c&&(Ne.DecompressionStream=new Te(c)),Le("CompressionStream",d),Le("DecompressionStream",u),_!==De){const{deflate:e,inflate:n}=_;if((e||n)&&(Ne.workerScripts||(Ne.workerScripts={})),e){if(!t.isArray(e))throw new f("workerScripts.deflate must be an array");Ne.workerScripts.deflate=e}if(n){if(!t.isArray(n))throw new f("workerScripts.inflate must be an array");Ne.workerScripts.inflate=n}}}function Le(e,t){t!==De&&(Ne[e]=t)}const Oe=[];for(let e=0;256>e;e++){let t=e;for(let e=0;8>e;e++)1&t?t=t>>>1^3988292384:t>>>=1;Oe[e]=t}class Me{constructor(e){this.crc=e||-1}append(e){let t=0|this.crc;for(let n=0,i=0|e.length;i>n;n++)t=t>>>8^Oe[255&(t^e[n])];this.crc=t}get(){return~this.crc}}class Pe extends z{constructor(){let e;const t=new Me;super({transform(e,n){t.append(e),n.enqueue(e)},flush(){const n=new _(4);new h(n.buffer).setUint32(0,t.get()),e.value=n}}),e=this}}function He(e){if(typeof m==Fe){const t=new _((e=unescape(encodeURIComponent(e))).length);for(let n=0;n0&&t&&(e[n-1]=Be.partial(t,e[n-1]&2147483648>>t-1,1)),e},partial:(e,t,n)=>32===e?t:(n?0|t:t<<32-e)+1099511627776*e,getPartial:e=>s.round(e/1099511627776)||32,_shiftRight(e,t,n,i){for(void 0===i&&(i=[]);t>=32;t-=32)i.push(n),n=0;if(0===t)return i.concat(e);for(let r=0;r>>t),n=e[r]<<32-t;const r=e.length?e[e.length-1]:0,a=Be.getPartial(r);return i.push(Be.partial(t+a&31,t+a>32?n:i.pop(),1)),i}},qe={bytes:{fromBits(e){const t=Be.bitLength(e)/8,n=new _(t);let i;for(let r=0;t>r;r++)3&r||(i=e[r/4]),n[r]=i>>>24,i<<=8;return n},toBits(e){const t=[];let n,i=0;for(n=0;n9007199254740991)throw new f("Cannot hash more than 2^53 - 1 bits");const a=new p(n);let s=0;for(let e=t.blockSize+i-(t.blockSize+i&t.blockSize-1);r>=e;e+=t.blockSize)t._block(a.subarray(16*s,16*(s+1))),s+=1;return n.splice(0,16*s),t}finalize(){const e=this;let t=e._buffer;const n=e._h;t=Be.concat(t,[Be.partial(1,1)]);for(let e=t.length+2;15&e;e++)t.push(0);for(t.push(s.floor(e._length/4294967296)),t.push(0|e._length);t.length;)e._block(t.splice(0,16));return e.reset(),n}_f(e,t,n,i){return e>19?e>39?e>59?e>79?void 0:t^n^i:t&n|t&i|n&i:t^n^i:t&n|~t&i}_S(e,t){return t<>>32-e}_block(e){const n=this,i=n._h,r=t(80);for(let t=0;16>t;t++)r[t]=e[t];let a=i[0],o=i[1],l=i[2],c=i[3],d=i[4];for(let e=0;79>=e;e++){16>e||(r[e]=n._S(1,r[e-3]^r[e-8]^r[e-14]^r[e-16]));const t=n._S(5,a)+n._f(e,o,l,c)+d+r[e]+n._key[s.floor(e/20)]|0;d=c,c=l,l=n._S(30,o),o=a,a=t}i[0]=i[0]+a|0,i[1]=i[1]+o|0,i[2]=i[2]+l|0,i[3]=i[3]+c|0,i[4]=i[4]+d|0}},Ze={getRandomValues(e){const t=new p(e.buffer),n=e=>{let t=987654321;const n=4294967295;return()=>(t=36969*(65535&t)+(t>>16)&n,(((t<<16)+(e=18e3*(65535&e)+(e>>16)&n)&n)/4294967296+.5)*(s.random()>.5?1:-1))};for(let i,r=0;rnew Ke.hmacSha1(qe.bytes.toBits(e)),pbkdf2(e,t,n,i){if(n=n||1e4,0>i||0>n)throw new f("invalid params to pbkdf2");const r=1+(i>>5)<<2;let a,s,o,l,c;const d=new ArrayBuffer(r),u=new h(d);let _=0;const w=Be;for(t=qe.bytes.toBits(t),c=1;(r||1)>_;c++){for(a=s=e.encrypt(w.concat(t,[c])),o=1;n>o;o++)for(s=e.encrypt(s),l=0;l_&&or&&(e=(new n).update(e).finalize());for(let t=0;r>t;t++)i[0][t]=909522486^e[t],i[1][t]=1549556828^e[t];t._baseHash[0].update(i[0]),t._baseHash[1].update(i[1]),t._resultHash=new n(t._baseHash[0])}reset(){const e=this;e._resultHash=new e._hash(e._baseHash[0]),e._updated=!1}update(e){this._updated=!0,this._resultHash.update(e)}digest(){const e=this,t=e._resultHash.finalize(),n=new e._hash(e._baseHash[1]).update(t).finalize();return e.reset(),n}encrypt(e){if(this._updated)throw new f("encrypt on already updated hmac called!");return this.update(e),this.digest(e)}}},Ye=typeof k!=Fe&&typeof k.getRandomValues==Re,Ge="Invalid password",Xe="Invalid signature",je="zipjs-abort-check-password";function Je(e){return Ye?k.getRandomValues(e):Ze.getRandomValues(e)}const Qe=16,$e={name:"PBKDF2"},et=n.assign({hash:{name:"HMAC"}},$e),tt=n.assign({iterations:1e3,hash:{name:"SHA-1"}},$e),nt=["deriveBits"],it=[8,12,16],rt=[16,24,32],at=10,st=[0,0,0,0],ot=typeof k!=Fe,lt=ot&&k.subtle,ct=ot&&typeof lt!=Fe,dt=qe.bytes,ut=class{constructor(e){const t=this;t._tables=[[[],[],[],[],[]],[[],[],[],[],[]]],t._tables[0][0][0]||t._precompute();const n=t._tables[0][4],i=t._tables[1],r=e.length;let a,s,o,l=1;if(4!==r&&6!==r&&8!==r)throw new f("invalid aes key size");for(t._key=[s=e.slice(0),o=[]],a=r;4*r+28>a;a++){let e=s[a-1];(a%r==0||8===r&&a%r==4)&&(e=n[e>>>24]<<24^n[e>>16&255]<<16^n[e>>8&255]<<8^n[255&e],a%r==0&&(e=e<<8^e>>>24^l<<24,l=l<<1^283*(l>>7))),s[a]=s[a-r]^e}for(let e=0;a;e++,a--){const t=s[3&e?a:a-4];o[e]=4>=a||4>e?t:i[0][n[t>>>24]]^i[1][n[t>>16&255]]^i[2][n[t>>8&255]]^i[3][n[255&t]]}}encrypt(e){return this._crypt(e,0)}decrypt(e){return this._crypt(e,1)}_precompute(){const e=this._tables[0],t=this._tables[1],n=e[4],i=t[4],r=[],a=[];let s,o,l,c;for(let e=0;256>e;e++)a[(r[e]=e<<1^283*(e>>7))^e]=e;for(let d=s=0;!n[d];d^=o||1,s=a[s]||1){let a=s^s<<1^s<<2^s<<3^s<<4;a=a>>8^255&a^99,n[d]=a,i[a]=d,c=r[l=r[o=r[d]]];let u=16843009*c^65537*l^257*o^16843008*d,f=257*r[a]^16843008*a;for(let n=0;4>n;n++)e[n][d]=f=f<<24^f>>>8,t[n][a]=u=u<<24^u>>>8}for(let n=0;5>n;n++)e[n]=e[n].slice(0),t[n]=t[n].slice(0)}_crypt(e,t){if(4!==e.length)throw new f("invalid aes block size");const n=this._key[t],i=n.length/4-2,r=[0,0,0,0],a=this._tables[t],s=a[0],o=a[1],l=a[2],c=a[3],d=a[4];let u,_,w,p=e[0]^n[0],h=e[t?3:1]^n[1],b=e[2]^n[2],g=e[t?1:3]^n[3],m=4;for(let e=0;i>e;e++)u=s[p>>>24]^o[h>>16&255]^l[b>>8&255]^c[255&g]^n[m],_=s[h>>>24]^o[b>>16&255]^l[g>>8&255]^c[255&p]^n[m+1],w=s[b>>>24]^o[g>>16&255]^l[p>>8&255]^c[255&h]^n[m+2],g=s[g>>>24]^o[p>>16&255]^l[h>>8&255]^c[255&b]^n[m+3],m+=4,p=u,h=_,b=w;for(let e=0;4>e;e++)r[t?3&-e:e]=d[p>>>24]<<24^d[h>>16&255]<<16^d[b>>8&255]<<8^d[255&g]^n[m++],u=p,p=h,h=b,b=g,g=u;return r}},ft=class{constructor(e,t){this._prf=e,this._initIv=t,this._iv=t}reset(){this._iv=this._initIv}update(e){return this.calculate(this._prf,e,this._iv)}incWord(e){if(255&~(e>>24))e+=1<<24;else{let t=e>>16&255,n=e>>8&255,i=255&e;255===t?(t=0,255===n?(n=0,255===i?i=0:++i):++n):++t,e=0,e+=t<<16,e+=n<<8,e+=i}return e}incCounter(e){0===(e[0]=this.incWord(e[0]))&&(e[1]=this.incWord(e[1]))}calculate(e,t,n){let i;if(!(i=t.length))return[];const r=Be.bitLength(t);for(let r=0;i>r;r+=4){this.incCounter(n);const i=e.encrypt(n);t[r]^=i[0],t[r+1]^=i[1],t[r+2]^=i[2],t[r+3]^=i[3]}return Be.clamp(t,r)}},_t=Ke.hmacSha1;let wt=ot&&ct&&typeof lt.importKey==Re,pt=ot&&ct&&typeof lt.deriveBits==Re;class ht extends z{constructor({password:e,rawPassword:t,signed:i,encryptionStrength:r,checkPasswordOnly:a}){super({start(){n.assign(this,{ready:new g((e=>this.resolveReady=e)),password:xt(e,t),signed:i,strength:r-1,pending:new _})},async transform(e,t){const n=this,{password:i,strength:r,resolveReady:s,ready:o}=n;i?(await(async(e,t,n,i)=>{const r=await mt(e,t,n,kt(i,0,it[t])),a=kt(i,it[t]);if(r[0]!=a[0]||r[1]!=a[1])throw new f(Ge)})(n,r,i,kt(e,0,it[r]+2)),e=kt(e,it[r]+2),a?t.error(new f(je)):s()):await o;const l=new _(e.length-at-(e.length-at)%Qe);t.enqueue(gt(n,e,l,0,at,!0))},async flush(e){const{signed:t,ctr:n,hmac:i,pending:r,ready:a}=this;if(i&&n){await a;const s=kt(r,0,r.length-at),o=kt(r,r.length-at);let l=new _;if(s.length){const e=zt(dt,s);i.update(e);const t=n.update(e);l=St(dt,t)}if(t){const e=kt(St(dt,i.digest()),0,at);for(let t=0;at>t;t++)if(e[t]!=o[t])throw new f(Xe)}e.enqueue(l)}}})}}class bt extends z{constructor({password:e,rawPassword:t,encryptionStrength:i}){let r;super({start(){n.assign(this,{ready:new g((e=>this.resolveReady=e)),password:xt(e,t),strength:i-1,pending:new _})},async transform(e,t){const n=this,{password:i,strength:r,resolveReady:a,ready:s}=n;let o=new _;i?(o=await(async(e,t,n)=>{const i=Je(new _(it[t]));return yt(i,await mt(e,t,n,i))})(n,r,i),a()):await s;const l=new _(o.length+e.length-e.length%Qe);l.set(o,0),t.enqueue(gt(n,e,l,o.length,0))},async flush(e){const{ctr:t,hmac:n,pending:i,ready:a}=this;if(n&&t){await a;let s=new _;if(i.length){const e=t.update(zt(dt,i));n.update(e),s=St(dt,e)}r.signature=St(dt,n.digest()).slice(0,at),e.enqueue(yt(s,r.signature))}}}),r=this}}function gt(e,t,n,i,r,a){const{ctr:s,hmac:o,pending:l}=e,c=t.length-r;let d;for(l.length&&(t=yt(l,t),n=((e,t)=>{if(t&&t>e.length){const n=e;(e=new _(t)).set(n,0)}return e})(n,c-c%Qe)),d=0;c-Qe>=d;d+=Qe){const e=zt(dt,kt(t,d,d+Qe));a&&o.update(e);const r=s.update(e);a||o.update(r),n.set(St(dt,r),d+i)}return e.pending=kt(t,d),n}async function mt(e,i,r,a){e.password=null;const s=await(async(e,t,n,i,r)=>{if(!wt)return Ke.importKey(t);try{return await lt.importKey("raw",t,n,!1,r)}catch(e){return wt=!1,Ke.importKey(t)}})(0,r,et,0,nt),o=await(async(e,t,n)=>{if(!pt)return Ke.pbkdf2(t,e.salt,tt.iterations,n);try{return await lt.deriveBits(e,t,n)}catch(i){return pt=!1,Ke.pbkdf2(t,e.salt,tt.iterations,n)}})(n.assign({salt:a},tt),s,8*(2*rt[i]+2)),l=new _(o),c=zt(dt,kt(l,0,rt[i])),d=zt(dt,kt(l,rt[i],2*rt[i])),u=kt(l,2*rt[i]);return n.assign(e,{keys:{key:c,authentication:d,passwordVerification:u},ctr:new ft(new ut(c),t.from(st)),hmac:new _t(d)}),u}function xt(e,t){return t===De?He(e):t}function yt(e,t){let n=e;return e.length+t.length&&(n=new _(e.length+t.length),n.set(e,0),n.set(t,e.length)),n}function kt(e,t,n){return e.subarray(t,n)}function St(e,t){return e.fromBits(t)}function zt(e,t){return e.toBits(t)}class vt extends z{constructor({password:e,passwordVerification:t,checkPasswordOnly:i}){super({start(){n.assign(this,{password:e,passwordVerification:t}),Rt(this,e)},transform(e,t){const n=this;if(n.password){const t=Dt(n,e.subarray(0,12));if(n.password=null,t[11]!=n.passwordVerification)throw new f(Ge);e=e.subarray(12)}i?t.error(new f(je)):t.enqueue(Dt(n,e))}})}}class Et extends z{constructor({password:e,passwordVerification:t}){super({start(){n.assign(this,{password:e,passwordVerification:t}),Rt(this,e)},transform(e,t){const n=this;let i,r;if(n.password){n.password=null;const t=Je(new _(12));t[11]=n.passwordVerification,i=new _(e.length+t.length),i.set(Ft(n,t),0),r=12}else i=new _(e.length),r=0;i.set(Ft(n,e),r),t.enqueue(i)}})}}function Dt(e,t){const n=new _(t.length);for(let i=0;i>>24]),r=~e.crcKey2.get(),e.keys=[n,i,r]}function At(e){const t=2|e.keys[2];return Ct(s.imul(t,1^t)>>>8)}function Ct(e){return 255&e}function Nt(e){return 4294967295&e}const Ut="deflate-raw";class Wt extends z{constructor(e,{chunkSize:t,CompressionStream:n,CompressionStreamNative:i}){super({});const{compressed:r,encrypted:a,useCompressionStream:s,zipCrypto:o,signed:l,level:c}=e,d=this;let u,f,_=Lt(super.readable);a&&!o||!l||(u=new Pe,_=Pt(_,u)),r&&(_=Mt(_,s,{level:c,chunkSize:t},i,n)),a&&(o?_=Pt(_,new Et(e)):(f=new bt(e),_=Pt(_,f))),Ot(d,_,(()=>{let e;a&&!o&&(e=f.signature),a&&!o||!l||(e=new h(u.value.buffer).getUint32(0)),d.signature=e}))}}class It extends z{constructor(e,{chunkSize:t,DecompressionStream:n,DecompressionStreamNative:i}){super({});const{zipCrypto:r,encrypted:a,signed:s,signature:o,compressed:l,useCompressionStream:c}=e;let d,u,_=Lt(super.readable);a&&(r?_=Pt(_,new vt(e)):(u=new ht(e),_=Pt(_,u))),l&&(_=Mt(_,c,{chunkSize:t},i,n)),a&&!r||!s||(d=new Pe,_=Pt(_,d)),Ot(this,_,(()=>{if((!a||r)&&s){const e=new h(d.value.buffer);if(o!=e.getUint32(0,!1))throw new f(Xe)}}))}}function Lt(e){return Pt(e,new z({transform(e,t){e&&e.length&&t.enqueue(e)}}))}function Ot(e,t,i){t=Pt(t,new z({flush:i})),n.defineProperty(e,"readable",{get:()=>t})}function Mt(e,t,n,i,r){try{e=Pt(e,new(t&&i?i:r)(Ut,n))}catch(i){if(!t)return e;try{e=Pt(e,new r(Ut,n))}catch(t){return e}}return e}function Pt(e,t){return e.pipeThrough(t)}const Ht="data",Bt="close",qt="deflate",Vt="inflate";class Zt extends z{constructor(e,t){super({});const i=this,{codecType:r}=e;let a;r.startsWith(qt)?a=Wt:r.startsWith(Vt)&&(a=It);let s=0,o=0;const l=new a(e,t),c=super.readable,d=new z({transform(e,t){e&&e.length&&(o+=e.length,t.enqueue(e))},flush(){n.assign(i,{inputSize:o})}}),u=new z({transform(e,t){e&&e.length&&(s+=e.length,t.enqueue(e))},flush(){const{signature:e}=l;n.assign(i,{signature:e,outputSize:s,inputSize:o})}});n.defineProperty(i,"readable",{get:()=>c.pipeThrough(d).pipeThrough(l).pipeThrough(u)})}}class Kt extends z{constructor(e){let t;super({transform:function n(i,r){if(t){const e=new _(t.length+i.length);e.set(t),e.set(i,t.length),i=e,t=null}i.length>e?(r.enqueue(i.slice(0,e)),n(i.slice(e),r)):t=i},flush(e){t&&t.length&&e.enqueue(t)}})}}let Yt=typeof T!=Fe;class Gt{constructor(e,{readable:t,writable:i},{options:r,config:a,streamOptions:s,useWebWorkers:o,transferStreams:l,scripts:c},d){const{signal:u}=s;return n.assign(e,{busy:!0,readable:t.pipeThrough(new Kt(a.chunkSize)).pipeThrough(new Xt(t,s),{signal:u}),writable:i,options:n.assign({},r),scripts:c,transferStreams:l,terminate:()=>new g((t=>{const{worker:n,busy:i}=e;n?(i?e.resolveTerminated=t:(n.terminate(),t()),e.interface=null):t()})),onTaskFinished(){const{resolveTerminated:t}=e;t&&(e.resolveTerminated=null,e.terminated=!0,e.worker.terminate(),t()),e.busy=!1,d(e)}}),(o&&Yt?Qt:Jt)(e,a)}}class Xt extends z{constructor(e,{onstart:t,onprogress:n,size:i,onend:r}){let a=0;super({async start(){t&&await jt(t,i)},async transform(e,t){a+=e.length,n&&await jt(n,a,i),t.enqueue(e)},async flush(){e.size=a,r&&await jt(r,a)}})}}async function jt(e,...t){try{await e(...t)}catch(e){}}function Jt(e,t){return{run:()=>(async({options:e,readable:t,writable:n,onTaskFinished:i},r)=>{try{const i=new Zt(e,r);await t.pipeThrough(i).pipeTo(n,{preventClose:!0,preventAbort:!0});const{signature:a,inputSize:s,outputSize:o}=i;return{signature:a,inputSize:s,outputSize:o}}finally{i()}})(e,t)}}function Qt(e,t){const{baseURL:i,chunkSize:r}=t;if(!e.interface){let a;try{a=((e,t,i)=>{const r={type:"module"};let a,s;typeof e==Re&&(e=e());try{a=new u(e,t)}catch(t){a=e}if($t)try{s=new T(a)}catch(e){$t=!1,s=new T(a,r)}else s=new T(a,r);return s.addEventListener("message",(e=>(async({data:e},t)=>{const{type:i,value:r,messageId:a,result:s,error:o}=e,{reader:l,writer:c,resolveResult:d,rejectResult:u,onTaskFinished:w}=t;try{if(o){const{message:e,stack:t,code:i,name:r}=o,a=new f(e);n.assign(a,{stack:t,code:i,name:r}),p(a)}else{if("pull"==i){const{value:e,done:n}=await l.read();tn({type:Ht,value:e,done:n,messageId:a},t)}i==Ht&&(await c.ready,await c.write(new _(r)),tn({type:"ack",messageId:a},t)),i==Bt&&p(null,s)}}catch(o){tn({type:Bt,messageId:a},t),p(o)}function p(e,t){e?u(e):d(t),c&&c.releaseLock(),w()}})(e,i))),s})(e.scripts[0],i,e)}catch(n){return Yt=!1,Jt(e,t)}n.assign(e,{worker:a,interface:{run:()=>(async(e,t)=>{let i,r;const a=new g(((e,t)=>{i=e,r=t}));n.assign(e,{reader:null,writer:null,resolveResult:i,rejectResult:r,result:a});const{readable:s,options:o,scripts:l}=e,{writable:c,closed:d}=(e=>{let t;const n=new g((e=>t=e));return{writable:new E({async write(t){const n=e.getWriter();await n.ready,await n.write(t),n.releaseLock()},close(){t()},abort:t=>e.getWriter().abort(t)}),closed:n}})(e.writable),u=tn({type:"start",scripts:l.slice(1),options:o,config:t,readable:s,writable:c},e);u||n.assign(e,{reader:s.getReader(),writer:c.getWriter()});const f=await a;return u||await c.getWriter().close(),await d,f})(e,{chunkSize:r})}})}return e.interface}let $t=!0,en=!0;function tn(e,{worker:t,writer:n,onTaskFinished:i,transferStreams:r}){try{const{value:n,readable:i,writable:a}=e,s=[];if(n&&(n.byteLength{const n=nn.find((e=>!e.busy));if(n)return on(n),new Gt(n,e,t,p);if(nn.length<_){const n={indexWorker:an};return an++,nn.push(n),new Gt(n,e,t,p)}return new g((n=>rn.push({resolve:n,stream:e,workerOptions:t})))})()).run();function p(e){if(rn.length){const[{resolve:t,stream:n,workerOptions:i}]=rn.splice(0,1);t(new Gt(e,n,i,p))}else e.worker?(on(e),((e,t)=>{const{config:n}=t,{terminateWorkerTimeout:i}=n;r.isFinite(i)&&i>=0&&(e.terminated?e.terminated=!1:e.terminateTimeout=setTimeout((async()=>{nn=nn.filter((t=>t!=e));try{await e.terminate()}catch(e){}}),i))})(e,t)):nn=nn.filter((t=>t!=e))}}function on(e){const{terminateTimeout:t}=e;t&&(clearTimeout(t),e.terminateTimeout=null)}const ln="HTTP error ",cn="HTTP Range not supported",dn="Writer iterator completed too soon",un="Range",fn="GET",_n="bytes",wn=65536,pn="writable";class hn{constructor(){this.size=0}init(){this.initialized=!0}}class bn extends hn{get readable(){const e=this,{chunkSize:t=wn}=e,n=new v({start(){this.chunkOffset=0},async pull(i){const{offset:r=0,size:a,diskNumberStart:o}=n,{chunkOffset:l}=this;i.enqueue(await Pn(e,r+l,s.min(t,a-l),o)),l+t>a?i.close():this.chunkOffset+=t}});return n}}class gn extends hn{constructor(){super();const e=this,t=new E({write:t=>e.writeUint8Array(t)});n.defineProperty(e,pn,{get:()=>t})}writeUint8Array(){}}class mn extends bn{constructor(e){super(),n.assign(this,{blob:e,size:e.size})}async readUint8Array(e,t){const n=this,i=e+t,r=e||it&&(a=a.slice(e,i)),new _(a)}}class xn extends hn{constructor(e){super();const t=new z,i=[];e&&i.push(["Content-Type",e]),n.defineProperty(this,pn,{get:()=>t.writable}),this.blob=new d(t.readable,{headers:i}).blob()}getData(){return this.blob}}class yn extends bn{constructor(e,t){super(),Sn(this,e,t)}async init(){await zn(this,Cn,Fn),super.init()}readUint8Array(e,t){return vn(this,e,t,Cn,Fn)}}class kn extends bn{constructor(e,t){super(),Sn(this,e,t)}async init(){await zn(this,Nn,Rn),super.init()}readUint8Array(e,t){return vn(this,e,t,Nn,Rn)}}function Sn(e,t,i){const{preventHeadRequest:r,useRangeHeader:a,forceRangeRequests:s,combineSizeEocd:o}=i;delete(i=n.assign({},i)).preventHeadRequest,delete i.useRangeHeader,delete i.forceRangeRequests,delete i.combineSizeEocd,delete i.useXHR,n.assign(e,{url:t,options:i,preventHeadRequest:r,useRangeHeader:a,forceRangeRequests:s,combineSizeEocd:o})}async function zn(e,t,n){const{url:i,preventHeadRequest:a,useRangeHeader:s,forceRangeRequests:o,combineSizeEocd:l}=e;if((e=>{const{baseURL:t}=Ue(),{protocol:n}=new u(e,t);return"http:"==n||"https:"==n})(i)&&(s||o)&&(void 0===a||a)){const i=await t(fn,e,En(e,l?-22:void 0));if(!o&&i.headers.get("Accept-Ranges")!=_n)throw new f(cn);{let a;l&&(e.eocdCache=new _(await i.arrayBuffer()));const s=i.headers.get("Content-Range");if(s){const e=s.trim().split(/\s*\/\s*/);if(e.length){const t=e[1];t&&"*"!=t&&(a=r(t))}}a===De?await An(e,t,n):e.size=a}}else await An(e,t,n)}async function vn(e,t,n,i,r){const{useRangeHeader:a,forceRangeRequests:s,eocdCache:o,size:l,options:c}=e;if(a||s){if(o&&t==l-ye&&n==ye)return o;const r=await i(fn,e,En(e,t,n));if(206!=r.status)throw new f(cn);return new _(await r.arrayBuffer())}{const{data:i}=e;return i||await r(e,c),new _(e.data.subarray(t,t+n))}}function En(e,t=0,i=1){return n.assign({},Dn(e),{[un]:_n+"="+(0>t?t:t+"-"+(t+i-1))})}function Dn({options:e}){const{headers:t}=e;if(t)return Symbol.iterator in t?n.fromEntries(t):t}async function Fn(e){await Tn(e,Cn)}async function Rn(e){await Tn(e,Nn)}async function Tn(e,t){const n=await t(fn,e,Dn(e));e.data=new _(await n.arrayBuffer()),e.size||(e.size=e.data.length)}async function An(e,t,n){if(e.preventHeadRequest)await n(e,e.options);else{const i=(await t("HEAD",e,Dn(e))).headers.get("Content-Length");i?e.size=r(i):await n(e,e.options)}}async function Cn(e,{options:t,url:i},r){const a=await fetch(i,n.assign({},t,{method:e,headers:r}));if(400>a.status)return a;throw 416==a.status?new f(cn):new f(ln+(a.statusText||a.status))}function Nn(e,{url:t},i){return new g(((r,a)=>{const s=new XMLHttpRequest;if(s.addEventListener("load",(()=>{if(400>s.status){const e=[];s.getAllResponseHeaders().trim().split(/[\r\n]+/).forEach((t=>{const n=t.trim().split(/\s*:\s*/);n[0]=n[0].trim().replace(/^[a-z]|-[a-z]/g,(e=>e.toUpperCase())),e.push(n)})),r({status:s.status,arrayBuffer:()=>s.response,headers:new l(e)})}else a(416==s.status?new f(cn):new f(ln+(s.statusText||s.status)))}),!1),s.addEventListener("error",(e=>a(e.detail?e.detail.error:new f("Network error"))),!1),s.open(e,t),i)for(const e of n.entries(i))s.setRequestHeader(e[0],e[1]);s.responseType="arraybuffer",s.send()}))}class Un extends bn{constructor(e,t={}){super(),n.assign(this,{url:e,reader:t.useXHR?new kn(e,t):new yn(e,t)})}set size(e){}get size(){return this.reader.size}async init(){await this.reader.init(),super.init()}readUint8Array(e,t){return this.reader.readUint8Array(e,t)}}class Wn extends bn{constructor(e){super(),this.readers=e}async init(){const e=this,{readers:t}=e;e.lastDiskNumber=0,e.lastDiskOffset=0,await g.all(t.map((async(n,i)=>{await n.init(),i!=t.length-1&&(e.lastDiskOffset+=n.size),e.size+=n.size}))),super.init()}async readUint8Array(e,t,n=0){const i=this,{readers:r}=this;let a,o=n;-1==o&&(o=r.length-1);let l=e;for(;l>=r[o].size;)l-=r[o].size,o++;const c=r[o],d=c.size;if(l+t>d){const r=d-l;a=new _(t),a.set(await Pn(c,l,r)),a.set(await i.readUint8Array(e+r,t-r,n),r)}else a=await Pn(c,l,t);return i.lastDiskNumber=s.max(o,i.lastDiskNumber),a}}class In extends hn{constructor(e,t=4294967295){super();const i=this;let r,a,s;n.assign(i,{diskNumber:0,diskOffset:0,size:0,maxSize:t,availableSize:t});const o=new E({async write(t){const{availableSize:n}=i;if(s)t.lengtho})}}async function Ln(e,t){if(!e.init||e.initialized)return g.resolve();await e.init(t)}function On(e){return t.isArray(e)&&(e=new Wn(e)),e instanceof v&&(e={readable:e}),e}function Mn(e){e.writable===De&&typeof e.next==Re&&(e=new In(e)),e instanceof E&&(e={writable:e});const{writable:t}=e;return t.size===De&&(t.size=0),e instanceof In||n.assign(e,{diskNumber:0,diskOffset:0,availableSize:1/0,maxSize:1/0}),e}function Pn(e,t,n,i){return e.readUint8Array(t,n,i)}const Hn=Wn,Bn=In,qn="\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ".split("");function Vn(e,t){return t&&"cp437"==t.trim().toLowerCase()?(e=>{{let t="";for(let n=0;nthis[t]=e[t]))}}const bi="File format is not recognized",gi="End of central directory not found",mi="End of Zip64 central directory locator not found",xi="Central directory header not found",yi="Local file header not found",ki="Zip64 extra field not found",Si="File contains encrypted entry",zi="Encryption method not supported",vi="Compression method not supported",Ei="Split zip file",Di="utf-8",Fi="cp437",Ri=[[Xn,fe],[jn,fe],[Jn,fe],[Qn,_e]],Ti={[_e]:{getValue:Hi,bytes:4},[fe]:{getValue:Bi,bytes:8}};class Ai{constructor(e,t={}){n.assign(this,{reader:On(e),options:t,config:Ue()})}async*getEntriesGenerator(e={}){const t=this;let{reader:i}=t;const{config:r}=t;if(await Ln(i),i.size!==De&&i.readUint8Array||(i=new mn(await new d(i.readable).blob()),await Ln(i)),i.size{const i=new _(4);var r;return r=t,qi(i).setUint32(0,r,!0),await a(22)||await a(s.min(1048582,n));async function a(t){const r=n-t,a=await Pn(e,r,t);for(let e=a.length-22;e>=0;e--)if(a[e]==i[0]&&a[e+1]==i[1]&&a[e+2]==i[2]&&a[e+3]==i[3])return{offset:r+e,buffer:a.slice(e,e+22).buffer}}})(i,ge,i.size);if(!a)throw Hi(qi(await Pn(i,0,4)))==pe?new f(Ei):new f(gi);const o=qi(a);let l=Hi(o,12),c=Hi(o,16);const u=a.offset,w=Pi(o,20),p=u+ye+w;let h=Pi(o,4);const b=i.lastDiskNumber||0;let g=Pi(o,6),m=Pi(o,8),x=0,y=0;if(c==fe||l==fe||m==_e||g==_e){const e=qi(await Pn(i,a.offset-20,20));if(Hi(e,0)==xe){c=Bi(e,8);let t=await Pn(i,c,56,-1),n=qi(t);const r=a.offset-20-56;if(Hi(n,0)!=me&&c!=r){const e=c;c=r,x=c-e,t=await Pn(i,c,56,-1),n=qi(t)}if(Hi(n,0)!=me)throw new f(mi);h==_e&&(h=Hi(n,16)),g==_e&&(g=Hi(n,20)),m==_e&&(m=Bi(n,32)),l==fe&&(l=Bi(n,40)),c-=l}}if(cc)throw new f(bi);let k=0,S=await Pn(i,c,l,g),z=qi(S);if(l){const e=a.offset-l;if(Hi(z,k)!=be&&c!=e){const t=c;c=e,x+=c-t,S=await Pn(i,c,l,g),z=qi(S)}}const v=a.offset-c-(i.lastDiskOffset||0);if(l==v||0>v||(l=v,S=await Pn(i,c,l,g),z=qi(S)),0>c||c>=i.size)throw new f(bi);const E=Ii(t,e,"filenameEncoding"),D=Ii(t,e,"commentEncoding");for(let a=0;m>a;a++){const o=new Ci(i,r,t.options);if(Hi(z,k)!=be)throw new f(xi);Ni(o,z,k+6);const l=!!o.bitFlag.languageEncodingFlag,c=k+46,d=c+o.filenameLength,u=d+o.extraFieldLength,_=Pi(z,k+4),w=!(_>>8),p=_>>8==3,h=S.subarray(c,d),b=Pi(z,k+32),g=u+b,v=S.subarray(u,g),F=l,R=l,T=Hi(z,k+38),A=w&&!(16&~Mi(z,k+38))||p&&!(16384&~(T>>16))||h.length&&47==h[h.length-1],C=p&&!(73&~(T>>16)),N=Hi(z,k+42)+x;n.assign(o,{versionMadeBy:_,msDosCompatible:w,compressedSize:0,uncompressedSize:0,commentLength:b,directory:A,offset:N,diskNumberStart:Pi(z,k+34),internalFileAttributes:Pi(z,k+36),externalFileAttributes:T,rawFilename:h,filenameUTF8:F,commentUTF8:R,rawExtraField:S.subarray(d,u),executable:C}),o.internalFileAttribute=o.internalFileAttributes,o.externalFileAttribute=o.externalFileAttributes;const U=Ii(t,e,"decodeText")||Vn,W=F?Di:E||Fi,I=R?Di:D||Fi;let L=U(h,W);L===De&&(L=Vn(h,W));let O=U(v,I);O===De&&(O=Vn(v,I)),n.assign(o,{rawComment:v,filename:L,comment:O,directory:A||L.endsWith(ze)}),y=s.max(N,y),Ui(o,o,z,k+6),o.zipCrypto=o.encrypted&&!o.extraFieldAES;const M=new hi(o);M.getData=(e,t)=>o.getData(e,M,t),k=g;const{onprogress:P}=e;if(P)try{await P(a+1,m,new hi(o))}catch(e){}yield M}const F=Ii(t,e,"extractPrependedData"),R=Ii(t,e,"extractAppendedData");return F&&(t.prependedData=y>0?await Pn(i,0,y):new _),t.comment=w?await Pn(i,u+ye,w):new _,R&&(t.appendedData=p>>8&255:w>>>24&255),signature:w,compressed:0!=c&&!k,encrypted:r.encrypted&&!k,useWebWorkers:Ii(r,i,"useWebWorkers"),useCompressionStream:Ii(r,i,"useCompressionStream"),transferStreams:Ii(r,i,"transferStreams"),checkPasswordOnly:T},config:d,streamOptions:{signal:R,size:D,onstart:C,onprogress:N,onend:U}};let I=0;try{({outputSize:I}=await sn({readable:F,writable:A},W))}catch(e){if(!T||e.message!=je)throw e}finally{const e=Ii(r,i,"preventClose");A.size+=I,e||A.locked||await A.getWriter().close()}return T?De:e.getData?e.getData():A}}function Ni(e,t,i){const r=e.rawBitFlag=Pi(t,i+2),a=!(1&~r),s=Hi(t,i+6);n.assign(e,{encrypted:a,version:Pi(t,i),bitFlag:{level:(6&r)>>1,dataDescriptor:!(8&~r),languageEncodingFlag:(r&Se)==Se},rawLastModDate:s,lastModDate:Li(s),filenameLength:Pi(t,i+22),extraFieldLength:Pi(t,i+24)})}function Ui(e,t,i,r,a){const{rawExtraField:s}=t,c=t.extraField=new l,d=qi(new _(s));let u=0;try{for(;u{t.zip64=!0;const n=qi(e.data),i=Ri.filter((([e,n])=>t[e]==n));for(let r=0,a=0;r{const r=qi(e.data),a=Mi(r,4);n.assign(e,{vendorVersion:Mi(r,0),vendorId:Mi(r,2),strength:a,originalCompressionMethod:i,compressionMethod:Pi(r,5)}),t.compressionMethod=e.compressionMethod})(g,t,w),t.extraFieldAES=g):t.compressionMethod=w;const m=c.get(10);m&&(((e,t)=>{const i=qi(e.data);let r,a=4;try{for(;a{const i=qi(e.data),r=Mi(i,0),a=[],s=[];n?(1&~r||(a.push($n),s.push(ei)),2&~r||(a.push(ti),s.push("rawLastAccessDate")),4&~r||(a.push(ni),s.push("rawCreationDate"))):5>e.data.length||(a.push($n),s.push(ei));let l=1;a.forEach(((n,r)=>{if(e.data.length>=l+4){const a=Hi(i,l);t[n]=e[n]=new o(1e3*a);const c=s[r];e[c]=a}l+=4}))})(x,t,a),t.extraFieldExtendedTimestamp=x);const y=c.get(6534);y&&(t.extraFieldUSDZ=y)}function Wi(e,t,i,r,a){const s=qi(e.data),o=new Me;o.append(a[i]);const l=qi(new _(4));l.setUint32(0,o.get(),!0);const c=Hi(s,1);n.assign(e,{version:Mi(s,0),[t]:Vn(e.data.subarray(5)),valid:!a.bitFlag.languageEncodingFlag&&c==Hi(l,0)}),e.valid&&(r[t]=e[t],r[t+"UTF8"]=!0)}function Ii(e,t,n){return t[n]===De?e.options[n]:t[n]}function Li(e){const t=(4294901760&e)>>16,n=65535&e;try{return new o(1980+((65024&t)>>9),((480&t)>>5)-1,31&t,(63488&n)>>11,(2016&n)>>5,2*(31&n),0)}catch(e){}}function Oi(e){return new o(r(e/a(1e4)-a(116444736e5)))}function Mi(e,t){return e.getUint8(t)}function Pi(e,t){return e.getUint16(t,!0)}function Hi(e,t){return e.getUint32(t,!0)}function Bi(e,t){return r(e.getBigUint64(t,!0))}function qi(e){return new h(e.buffer)}const Vi="File already exists",Zi="Zip file comment exceeds 64KB",Ki="File entry comment exceeds 64KB",Yi="File entry name exceeds 64KB",Gi="Version exceeds 65535",Xi="The strength must equal 1, 2, or 3",ji="Extra field type exceeds 65535",Ji="Extra field data exceeds 64KB",Qi="Zip64 is not supported (make sure 'keepOrder' is set to 'true')",$i="Undefined uncompressed size",er=new _([7,0,2,0,65,69,3,0,0]);let tr=0;const nr=[];class ir{constructor(e,t={}){const i=(e=Mn(e)).availableSize!==De&&e.availableSize>0&&e.availableSize!==1/0&&e.maxSize!==De&&e.maxSize>0&&e.maxSize!==1/0;n.assign(this,{writer:e,addSplitZipSignature:i,options:t,config:Ue(),files:new l,filenames:new c,offset:t.offset===De?e.writable.size:t.offset,pendingEntriesSize:0,pendingAddFileCalls:new c,bufferedWrites:0})}async add(e="",i,r={}){const l=this,{pendingAddFileCalls:c,config:u}=l;let b;trnr.push(e)));try{if(e=e.trim(),l.filenames.has(e))throw new f(Vi);return l.filenames.add(e),b=(async(e,i,r,l)=>{i=i.trim();const c=sr(e,l,oi),u=sr(e,l,ui,c?20:768),b=sr(e,l,wi);if(u>_e)throw new f(Gi);let m=sr(e,l,si,0);0===m&&(m=sr(e,l,ai,0)),!l.directory&&i.endsWith(ze)&&(l.directory=!0),sr(e,l,_i)?(i.endsWith(ze)||(i+=ze),0===m&&(m=c?16:1073741824)):c||0!==m||(m=b?493<<16:27525120);const x=sr(e,l,"encodeText",He);let y=x(i);if(y===De&&(y=He(i)),wr(y)>_e)throw new f(Yi);const k=l.comment||"";let S=x(k);if(S===De&&(S=He(k)),wr(S)>_e)throw new f(Ki);const v=sr(e,l,di,20);if(v>_e)throw new f(Gi);const E=sr(e,l,$n,new o),D=sr(e,l,ti),F=sr(e,l,ni);let R=sr(e,l,ri,0);0===R&&(R=sr(e,l,ii,0));const T=sr(e,l,"passThrough");let A,C;T||(A=sr(e,l,"password"),C=sr(e,l,"rawPassword"));const N=sr(e,l,"encryptionStrength",3),U=sr(e,l,fi),W=sr(e,l,"extendedTimestamp",!0),I=sr(e,l,"keepOrder",!0),L=sr(e,l,"level"),O=sr(e,l,"useWebWorkers"),M=sr(e,l,"bufferedWrite"),P=sr(e,l,"dataDescriptorSignature",!1),H=sr(e,l,"signal"),B=sr(e,l,"useUnicodeFileNames",!0),q=sr(e,l,"useCompressionStream"),V=sr(e,l,"compressionMethod");let Z=sr(e,l,"dataDescriptor",!0),K=sr(e,l,li);if(!U&&(A!==De||C!==De)&&(1>N||N>3))throw new f(Xi);let Y=new _;const{extraField:G}=l;if(G){let e=0,t=0;G.forEach((t=>e+=4+wr(t))),Y=new _(e),G.forEach(((e,n)=>{if(n>_e)throw new f(ji);if(wr(e)>_e)throw new f(Ji);fr(Y,new w([n]),t),fr(Y,new w([wr(e)]),t+2),fr(Y,e,t+4),t+=4+wr(e)}))}let X=0,j=0,J=0;if(T&&(({uncompressedSize:J}=l),J===De))throw new f($i);const Q=!0===K;r&&(r=On(r),await Ln(r),T?X=or(J):r.size===De?(Z=!0,(K||K===De)&&(K=!0,J=X=4294967296)):(J=r.size,X=or(J)));const{diskOffset:$,diskNumber:ee,maxSize:te}=e.writer,ne=Q||J>fe,ie=Q||X>fe,re=Q||e.offset+e.pendingEntriesSize-$>fe,ae=sr(e,l,"supportZip64SplitFile",!0)&&Q||ee+s.ceil(e.pendingEntriesSize/te)>_e;if(re||ne||ie||ae){if(!1===K||!I)throw new f(Qi);K=!0}K=K||!1;const se=sr(e,l,ci),{signature:oe}=l,le=(e=>{const{rawFilename:t,lastModDate:n,lastAccessDate:i,creationDate:r,level:a,zip64:o,zipCrypto:l,useUnicodeFileNames:c,dataDescriptor:d,directory:u,rawExtraField:f,encryptionStrength:w,extendedTimestamp:h,encrypted:b}=e;let{version:g,compressionMethod:m}=e;const x=!u&&(a>0||a===De&&0!==m);let y,k,S,z;if(b&&!l){y=new _(wr(er)+2);const e=_r(y);cr(e,0,39169),fr(y,er,2),lr(e,8,w)}else y=new _;if(h){S=new _(9+(i?4:0)+(r?4:0));const e=_r(S);cr(e,0,ke),cr(e,2,wr(S)-4),z=1+(i?2:0)+(r?4:0),lr(e,4,z);let t=5;dr(e,t,s.floor(n.getTime()/1e3)),t+=4,i&&(dr(e,t,s.floor(i.getTime()/1e3)),t+=4),r&&dr(e,t,s.floor(r.getTime()/1e3));try{k=new _(36);const e=_r(k),t=ar(n);cr(e,0,10),cr(e,2,32),cr(e,8,1),cr(e,10,24),ur(e,12,t),ur(e,20,ar(i)||t),ur(e,28,ar(r)||t)}catch(e){k=new _}}else k=S=new _;let v=0;c&&(v|=Se),d&&(v|=8),m===De&&(m=x?8:0),8==m&&(a>=1&&3>a&&(v|=6),a>=3&&5>a&&(v|=1),9===a&&(v|=2)),o&&(g=g>45?g:45),b&&(v|=1,l||(g=g>51?g:51,y[9]=m,m=99));const E=new _(26),D=_r(E);cr(D,0,g),cr(D,2,v),cr(D,4,m);const F=new p(1),R=_r(F);let T;T=Ee>n?Ee:n>ve?ve:n,cr(R,0,(T.getHours()<<6|T.getMinutes())<<5|T.getSeconds()/2),cr(R,2,(T.getFullYear()-1980<<4|T.getMonth()+1)<<5|T.getDate());const A=F[0];dr(D,6,A),cr(D,22,wr(t));const C=wr(y,S,k,f);cr(D,24,C);const N=new _(30+wr(t)+C);return dr(_r(N),0,we),fr(N,E,4),fr(N,t,30),fr(N,y,30+wr(t)),fr(N,S,30+wr(t,y)),fr(N,k,30+wr(t,y,S)),fr(N,f,30+wr(t,y,S,k)),{localHeaderArray:N,headerArray:E,headerView:D,lastModDate:n,rawLastModDate:A,encrypted:b,compressed:x,version:g,compressionMethod:m,extraFieldExtendedTimestampFlag:z,rawExtraFieldExtendedTimestamp:S,rawExtraFieldNTFS:k,rawExtraFieldAES:y,extraFieldLength:C}})(l=n.assign({},l,{rawFilename:y,rawComment:S,version:v,versionMadeBy:u,lastModDate:E,lastAccessDate:D,creationDate:F,rawExtraField:Y,zip64:K,zip64UncompressedSize:ne,zip64CompressedSize:ie,zip64Offset:re,zip64DiskNumberStart:ae,password:A,rawPassword:C,level:q||e.config.CompressionStream!==De||e.config.CompressionStreamNative!==De?L:0,useWebWorkers:O,encryptionStrength:N,extendedTimestamp:W,zipCrypto:U,bufferedWrite:M,keepOrder:I,useUnicodeFileNames:B,dataDescriptor:Z,dataDescriptorSignature:P,signal:H,msDosCompatible:c,internalFileAttribute:R,internalFileAttributes:R,externalFileAttribute:m,externalFileAttributes:m,useCompressionStream:q,passThrough:T,encrypted:!!(A&&wr(A)||C&&wr(C))||T&&se,signature:oe,compressionMethod:V})),ce=(e=>{const{zip64:t,dataDescriptor:n,dataDescriptorSignature:i}=e;let r,a=new _,s=0;return n&&(a=new _(t?i?24:20:i?16:12),r=_r(a),i&&(s=4,dr(r,0,he))),{dataDescriptorArray:a,dataDescriptorView:r,dataDescriptorOffset:s}})(l),de=wr(le.localHeaderArray,ce.dataDescriptorArray);let ue;j=de+X,e.options.usdz&&(j+=j+64),e.pendingEntriesSize+=j;try{ue=await(async(e,i,r,s,o)=>{const{files:l,writer:c}=e,{keepOrder:u,dataDescriptor:w,signal:p}=o,{headerInfo:b}=s,{usdz:m}=e.options,x=t.from(l.values()).pop();let y,k,S,v,E,D,F,R={};l.set(i,R);try{let t;u&&(t=x&&x.lock,R.lock=new g((e=>S=e))),!(o.bufferedWrite||e.writerLocked||e.bufferedWrites&&u)&&w||m?(D=c,await T()):(D=new z,F=new d(D.readable).blob(),D.writable.size=0,y=!0,e.bufferedWrites++,await Ln(c)),await Ln(D);const{writable:b}=c;let{diskOffset:k}=c;if(e.addSplitZipSignature){delete e.addSplitZipSignature;const t=new _(4);dr(_r(t),0,pe),await rr(b,t),e.offset+=4}m&&((e,t)=>{const{headerInfo:n}=e;let{localHeaderArray:i,extraFieldLength:r}=n,a=_r(i),s=64-(t+wr(i))%64;4>s&&(s+=64);const o=new _(s),l=_r(o);cr(l,0,6534),cr(l,2,s-2);const c=i;n.localHeaderArray=i=new _(wr(c)+s),fr(i,c),fr(i,o,wr(c)),a=_r(i),cr(a,28,r+s),e.metadataSize+=s})(s,e.offset-k),y||(await t,await A(b));const{diskNumber:C}=c;if(E=!0,R.diskNumberStart=C,R=await(async(e,t,{diskNumberStart:i,lock:r},s,o,l)=>{const{headerInfo:c,dataDescriptorInfo:d,metadataSize:u}=s,{localHeaderArray:f,headerArray:w,lastModDate:p,rawLastModDate:h,encrypted:b,compressed:g,version:m,compressionMethod:x,rawExtraFieldExtendedTimestamp:y,extraFieldExtendedTimestampFlag:k,rawExtraFieldNTFS:S,rawExtraFieldAES:z}=c,{dataDescriptorArray:v}=d,{rawFilename:E,lastAccessDate:D,creationDate:F,password:R,rawPassword:T,level:A,zip64:C,zip64UncompressedSize:N,zip64CompressedSize:U,zip64Offset:W,zip64DiskNumberStart:I,zipCrypto:L,dataDescriptor:O,directory:M,executable:P,versionMadeBy:H,rawComment:B,rawExtraField:q,useWebWorkers:V,onstart:Z,onprogress:K,onend:Y,signal:G,encryptionStrength:X,extendedTimestamp:j,msDosCompatible:J,internalFileAttributes:Q,externalFileAttributes:$,useCompressionStream:ee,passThrough:te}=l,ne={lock:r,versionMadeBy:H,zip64:C,directory:!!M,executable:!!P,filenameUTF8:!0,rawFilename:E,commentUTF8:!0,rawComment:B,rawExtraFieldExtendedTimestamp:y,rawExtraFieldNTFS:S,rawExtraFieldAES:z,rawExtraField:q,extendedTimestamp:j,msDosCompatible:J,internalFileAttributes:Q,externalFileAttributes:$,diskNumberStart:i};let{signature:ie,uncompressedSize:re}=l,ae=0;te||(re=0);const{writable:se}=t;if(e){e.chunkSize=We(o),await rr(se,f);const t=e.readable,n=t.size=e.size,i={options:{codecType:qt,level:A,rawPassword:T,password:R,encryptionStrength:X,zipCrypto:b&&L,passwordVerification:b&&L&&h>>8&255,signed:!te,compressed:g&&!te,encrypted:b&&!te,useWebWorkers:V,useCompressionStream:ee,transferStreams:!1},config:o,streamOptions:{signal:G,size:n,onstart:Z,onprogress:K,onend:Y}},r=await sn({readable:t,writable:se},i);ae=r.outputSize,te||(re=r.inputSize,ie=r.signature),se.size+=re}else await rr(se,f);let oe;if(C){let e=4;N&&(e+=8),U&&(e+=8),W&&(e+=8),I&&(e+=4),oe=new _(e)}else oe=new _;return((e,t)=>{const{signature:n,rawExtraFieldZip64:i,compressedSize:r,uncompressedSize:s,headerInfo:o,dataDescriptorInfo:l}=e,{headerView:c,encrypted:d}=o,{dataDescriptorView:u,dataDescriptorOffset:f}=l,{zip64:_,zip64UncompressedSize:w,zip64CompressedSize:p,zipCrypto:h,dataDescriptor:b}=t;if(d&&!h||n===De||(dr(c,10,n),b&&dr(u,f,n)),_){const e=_r(i);cr(e,0,1),cr(e,2,wr(i)-4);let t=4;w&&(dr(c,18,fe),ur(e,t,a(s)),t+=8),p&&(dr(c,14,fe),ur(e,t,a(r))),b&&(ur(u,f+4,a(r)),ur(u,f+12,a(s)))}else dr(c,14,r),dr(c,18,s),b&&(dr(u,f+4,r),dr(u,f+8,s))})({signature:ie,rawExtraFieldZip64:oe,compressedSize:ae,uncompressedSize:re,headerInfo:c,dataDescriptorInfo:d},l),O&&await rr(se,v),n.assign(ne,{uncompressedSize:re,compressedSize:ae,lastModDate:p,rawLastModDate:h,creationDate:F,lastAccessDate:D,encrypted:b,zipCrypto:L,size:u+ae,compressionMethod:x,version:m,headerArray:w,signature:ie,rawExtraFieldZip64:oe,extraFieldExtendedTimestampFlag:k,zip64UncompressedSize:N,zip64CompressedSize:U,zip64Offset:W,zip64DiskNumberStart:I}),ne})(r,D,R,s,e.config,o),E=!1,l.set(i,R),R.filename=i,y){await D.writable.getWriter().close();let e=await F;await t,await T(),v=!0,w||(e=await(async(e,t,n,{zipCrypto:i})=>{let r;r=await t.slice(0,26).arrayBuffer(),26!=r.byteLength&&(r=r.slice(0,26));const a=new h(r);return e.encrypted&&!i||dr(a,14,e.signature),e.zip64?(dr(a,18,fe),dr(a,22,fe)):(dr(a,18,e.compressedSize),dr(a,22,e.uncompressedSize)),await rr(n,new _(r)),t.slice(r.byteLength)})(R,e,b,o)),await A(b),R.diskNumberStart=c.diskNumber,k=c.diskOffset,await e.stream().pipeTo(b,{preventClose:!0,preventAbort:!0,signal:p}),b.size+=e.size,v=!1}if(R.offset=e.offset-k,R.zip64)((e,t)=>{const{rawExtraFieldZip64:n,offset:i,diskNumberStart:r}=e,{zip64UncompressedSize:s,zip64CompressedSize:o,zip64Offset:l,zip64DiskNumberStart:c}=t,d=_r(n);let u=4;s&&(u+=8),o&&(u+=8),l&&(ur(d,u,a(i)),u+=8),c&&dr(d,u,r)})(R,o);else if(R.offset>fe)throw new f(Qi);return e.offset+=R.size,R}catch(t){if(y&&v||!y&&E){if(e.hasCorruptedEntries=!0,t)try{t.corruptedEntry=!0}catch(e){}y?e.offset+=D.writable.size:e.offset=D.writable.size}throw l.delete(i),t}finally{y&&e.bufferedWrites--,S&&S(),k&&k()}async function T(){e.writerLocked=!0;const{lockWriter:t}=e;e.lockWriter=new g((t=>k=()=>{e.writerLocked=!1,t()})),await t}async function A(e){wr(b.localHeaderArray)>c.availableSize&&(c.availableSize=0,await rr(e,new _))}})(e,i,r,{headerInfo:le,dataDescriptorInfo:ce,metadataSize:de},l)}finally{e.pendingEntriesSize-=j}return n.assign(ue,{name:i,comment:k,extraField:G}),new hi(ue)})(l,e,i,r),c.add(b),await b}catch(t){throw l.filenames.delete(e),t}finally{c.delete(b);const e=nr.shift();e?e():tr--}}async close(e=new _,n={}){const{pendingAddFileCalls:i,writer:r}=this,{writable:o}=r;for(;i.size;)await g.allSettled(t.from(i));return await(async(e,n,i)=>{const{files:r,writer:o}=e,{diskOffset:l,writable:c}=o;let{diskNumber:d}=o,u=0,w=0,p=e.offset-l,h=r.size;for(const[,e]of r){const{rawFilename:t,rawExtraFieldZip64:n,rawExtraFieldAES:i,rawComment:r,rawExtraFieldNTFS:a,rawExtraField:o,extendedTimestamp:l,extraFieldExtendedTimestampFlag:c,lastModDate:d}=e;let u;if(l){u=new _(9);const e=_r(u);cr(e,0,ke),cr(e,2,5),lr(e,4,c),dr(e,5,s.floor(d.getTime()/1e3))}else u=new _;e.rawExtraFieldCDExtendedTimestamp=u,w+=46+wr(t,r,n,i,a,u,o)}const b=new _(w),g=_r(b);await Ln(o);let m=0;for(const[e,n]of t.from(r.values()).entries()){const{offset:t,rawFilename:a,rawExtraFieldZip64:s,rawExtraFieldAES:l,rawExtraFieldCDExtendedTimestamp:d,rawExtraFieldNTFS:f,rawExtraField:_,rawComment:w,versionMadeBy:p,headerArray:h,zip64:x,zip64UncompressedSize:y,zip64CompressedSize:k,zip64DiskNumberStart:S,zip64Offset:z,internalFileAttributes:v,externalFileAttributes:E,diskNumberStart:D,uncompressedSize:F,compressedSize:R}=n,T=wr(s,l,d,f,_);dr(g,u,be),cr(g,u+4,p);const A=_r(h);y||dr(A,18,F),k||dr(A,14,R),fr(b,h,u+6),cr(g,u+30,T),cr(g,u+32,wr(w)),cr(g,u+34,x&&S?_e:D),cr(g,u+36,v),E&&dr(g,u+38,E),dr(g,u+42,x&&z?fe:t),fr(b,a,u+46),fr(b,s,u+46+wr(a)),fr(b,l,u+46+wr(a,s)),fr(b,d,u+46+wr(a,s,l)),fr(b,f,u+46+wr(a,s,l,d)),fr(b,_,u+46+wr(a,s,l,d,f)),fr(b,w,u+46+wr(a)+T);const C=46+wr(a,w)+T;if(u-m>o.availableSize&&(o.availableSize=0,await rr(c,b.slice(m,u)),m=u),u+=C,i.onprogress)try{await i.onprogress(e+1,r.size,new hi(n))}catch(e){}}await rr(c,m?b.slice(m):b);let x=o.diskNumber;const{availableSize:y}=o;ye>y&&x++;let k=sr(e,i,li);if(p>fe||w>fe||h>_e||x>_e){if(!1===k)throw new f(Qi);k=!0}const S=new _(k?98:ye),z=_r(S);u=0,k&&(dr(z,0,me),ur(z,4,a(44)),cr(z,12,45),cr(z,14,45),dr(z,16,x),dr(z,20,d),ur(z,24,a(h)),ur(z,32,a(h)),ur(z,40,a(w)),ur(z,48,a(p)),dr(z,56,xe),ur(z,64,a(p)+a(w)),dr(z,72,x+1),sr(e,i,"supportZip64SplitFile",!0)&&(x=_e,d=_e),h=_e,p=fe,w=fe,u+=76),dr(z,u,ge),cr(z,u+4,x),cr(z,u+6,d),cr(z,u+8,h),cr(z,u+10,h),dr(z,u+12,w),dr(z,u+16,p);const v=wr(n);if(v){if(v>_e)throw new f(Zi);cr(z,u+20,v)}await rr(c,S),v&&await rr(c,n)})(this,e,n),sr(this,n,"preventClose")||await o.getWriter().close(),r.getData?r.getData():o}}async function rr(e,t){const n=e.getWriter();try{await n.ready,e.size+=wr(t),await n.write(t)}finally{n.releaseLock()}}function ar(e){if(e)return(a(e.getTime())+a(116444736e5))*a(1e4)}function sr(e,t,n,i){const r=t[n]===De?e.options[n]:t[n];return r===De?i:r}function or(e){return e+5*(s.floor(e/16383)+1)}function lr(e,t,n){e.setUint8(t,n)}function cr(e,t,n){e.setUint16(t,n,!0)}function dr(e,t,n){e.setUint32(t,n,!0)}function ur(e,t,n){e.setBigUint64(t,n,!0)}function fr(e,t,n){e.set(t,n)}function _r(e){return new h(e.buffer)}function wr(...e){let t=0;return e.forEach((e=>e&&(t+=e.length))),t}Ie({Deflate:function(e){const t=new Y,n=(i=e&&e.chunkSize?e.chunkSize:65536)+5*(s.floor(i/16383)+1);var i;const r=new _(n);let a=e?e.level:-1;void 0===a&&(a=-1),t.deflateInit(a),t.next_out=r,this.append=(e,i)=>{let a,s,o=0,l=0,c=0;const d=[];if(e.length){t.next_in_index=0,t.next_in=e,t.avail_in=e.length;do{if(t.next_out_index=0,t.avail_out=n,a=t.deflate(0),0!=a)throw new f("deflating: "+t.msg);t.next_out_index&&(t.next_out_index==n?d.push(new _(r)):d.push(r.subarray(0,t.next_out_index))),c+=t.next_out_index,i&&t.next_in_index>0&&t.next_in_index!=o&&(i(t.next_in_index),o=t.next_in_index)}while(t.avail_in>0||0===t.avail_out);return d.length>1?(s=new _(c),d.forEach((e=>{s.set(e,l),l+=e.length}))):s=d[0]?new _(d[0]):new _,s}},this.flush=()=>{let e,i,a=0,s=0;const o=[];do{if(t.next_out_index=0,t.avail_out=n,e=t.deflate(4),1!=e&&0!=e)throw new f("deflating: "+t.msg);n-t.avail_out>0&&o.push(r.slice(0,t.next_out_index)),s+=t.next_out_index}while(t.avail_in>0||0===t.avail_out);return t.deflateEnd(),i=new _(s),o.forEach((e=>{i.set(e,a),a+=e.length})),i}},Inflate:function(e){const t=new ue,n=e&&e.chunkSize?s.floor(2*e.chunkSize):131072,i=new _(n);let r=!1;t.inflateInit(),t.next_out=i,this.append=(e,a)=>{const s=[];let o,l,c=0,d=0,u=0;if(0!==e.length){t.next_in_index=0,t.next_in=e,t.avail_in=e.length;do{if(t.next_out_index=0,t.avail_out=n,0!==t.avail_in||r||(t.next_in_index=0,r=!0),o=t.inflate(0),r&&o===j){if(0!==t.avail_in)throw new f("inflating: bad input")}else if(0!==o&&1!==o)throw new f("inflating: "+t.msg);if((r||1===o)&&t.avail_in===e.length)throw new f("inflating: bad input");t.next_out_index&&(t.next_out_index===n?s.push(new _(i)):s.push(i.subarray(0,t.next_out_index))),u+=t.next_out_index,a&&t.next_in_index>0&&t.next_in_index!=c&&(a(t.next_in_index),c=t.next_in_index)}while(t.avail_in>0||0===t.avail_out);return s.length>1?(l=new _(u),s.forEach((e=>{l.set(e,d),d+=e.length}))):l=s[0]?new _(s[0]):new _,l}},this.flush=()=>{t.inflateEnd()}}}),e.BlobReader=mn,e.BlobWriter=xn,e.Data64URIReader=class extends bn{constructor(e){super();let t=e.length;for(;"="==e.charAt(t-1);)t--;const i=e.indexOf(",")+1;n.assign(this,{dataURI:e,dataStart:i,size:s.floor(.75*(t-i))})}readUint8Array(e,t){const{dataStart:n,dataURI:i}=this,r=new _(t),a=4*s.floor(e/3),o=atob(i.substring(a+n,4*s.ceil((e+t)/3)+n)),l=e-3*s.floor(a/4);for(let e=l;l+t>e;e++)r[e-l]=o.charCodeAt(e);return r}},e.Data64URIWriter=class extends gn{constructor(e){super(),n.assign(this,{data:"data:"+(e||"")+";base64,",pending:[]})}writeUint8Array(e){const t=this;let n=0,r=t.pending;const a=t.pending.length;for(t.pending="",n=0;n<3*s.floor((a+e.length)/3)-a;n++)r+=i.fromCharCode(e[n]);for(;n2?t.data+=S(r):t.pending=r}getData(){return this.data+S(this.pending)}},e.ERR_BAD_FORMAT=bi,e.ERR_CENTRAL_DIRECTORY_NOT_FOUND=xi,e.ERR_DUPLICATED_NAME=Vi,e.ERR_ENCRYPTED=Si,e.ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND=mi,e.ERR_EOCDR_NOT_FOUND=gi,e.ERR_EXTRAFIELD_ZIP64_NOT_FOUND=ki,e.ERR_HTTP_RANGE=cn,e.ERR_INVALID_COMMENT=Zi,e.ERR_INVALID_ENCRYPTION_STRENGTH=Xi,e.ERR_INVALID_ENTRY_COMMENT=Ki,e.ERR_INVALID_ENTRY_NAME=Yi,e.ERR_INVALID_EXTRAFIELD_DATA=Ji,e.ERR_INVALID_EXTRAFIELD_TYPE=ji,e.ERR_INVALID_PASSWORD=Ge,e.ERR_INVALID_SIGNATURE=Xe,e.ERR_INVALID_VERSION=Gi,e.ERR_ITERATOR_COMPLETED_TOO_SOON=dn,e.ERR_LOCAL_FILE_HEADER_NOT_FOUND=yi,e.ERR_SPLIT_ZIP_FILE=Ei,e.ERR_UNDEFINED_UNCOMPRESSED_SIZE=$i,e.ERR_UNSUPPORTED_COMPRESSION=vi,e.ERR_UNSUPPORTED_ENCRYPTION=zi,e.ERR_UNSUPPORTED_FORMAT=Qi,e.HttpRangeReader=class extends Un{constructor(e,t={}){t.useRangeHeader=!0,super(e,t)}},e.HttpReader=Un,e.Reader=bn,e.SplitDataReader=Wn,e.SplitDataWriter=In,e.SplitZipReader=Hn,e.SplitZipWriter=Bn,e.TextReader=class extends mn{constructor(e){super(new b([e],{type:"text/plain"}))}},e.TextWriter=class extends xn{constructor(e){super(e),n.assign(this,{encoding:e,utf8:!e||"utf-8"==e.toLowerCase()})}async getData(){const{encoding:e,utf8:t}=this,i=await super.getData();if(i.text&&t)return i.text();{const t=new FileReader;return new g(((r,a)=>{n.assign(t,{onload:({target:e})=>r(e.result),onerror:()=>a(t.error)}),t.readAsText(i,e)}))}}},e.Uint8ArrayReader=class extends bn{constructor(e){super(),n.assign(this,{array:e,size:e.length})}readUint8Array(e,t){return this.array.slice(e,e+t)}},e.Uint8ArrayWriter=class extends gn{init(e=0){n.assign(this,{offset:0,array:new _(e)}),super.init()}writeUint8Array(e){const t=this;if(t.offset+e.length>t.array.length){const n=t.array;t.array=new _(n.length+e.length),t.array.set(n)}t.array.set(e,t.offset),t.offset+=e.length}getData(){return this.array}},e.Writer=gn,e.ZipReader=Ai,e.ZipReaderStream=class{constructor(e={}){const{readable:t,writable:n}=new z,i=new Ai(t,e).getEntriesGenerator();this.readable=new v({async pull(e){const{done:t,value:n}=await i.next();if(t)return e.close();const r={...n,readable:(()=>{const{readable:e,writable:t}=new z;if(n.getData)return n.getData(t),e})()};delete r.getData,e.enqueue(r)}}),this.writable=n}},e.ZipWriter=ir,e.ZipWriterStream=class{constructor(e={}){const{readable:t,writable:n}=new z;this.readable=t,this.zipWriter=new ir(n,e)}transform(e){const{readable:t,writable:n}=new z({flush:()=>{this.zipWriter.close()}});return this.zipWriter.add(e,t),{readable:this.readable,writable:n}}writable(e){const{readable:t,writable:n}=new z;return this.zipWriter.add(e,t),n}close(e,t={}){return this.zipWriter.close(e,t)}},e.configure=Ie,e.getMimeType=()=>"application/octet-stream",e.initReader=On,e.initStream=Ln,e.initWriter=Mn,e.readUint8Array=Pn,e.terminateWorkers=async()=>{await g.allSettled(nn.map((e=>(on(e),e.terminate()))))}})); + diff --git a/pkg/emscripten/libretro/index.html b/pkg/emscripten/libretro/index.html index 9e2f41882c..31cd1bda9b 100644 --- a/pkg/emscripten/libretro/index.html +++ b/pkg/emscripten/libretro/index.html @@ -192,10 +192,10 @@ RetroArch Logo - - - - + + + + diff --git a/pkg/emscripten/libretro/libretro.css b/pkg/emscripten/libretro/libretro.css index ec3d514e3d..4d08dc2eb4 100644 --- a/pkg/emscripten/libretro/libretro.css +++ b/pkg/emscripten/libretro/libretro.css @@ -81,22 +81,12 @@ } } +/** + * Disable the border around the player. + */ canvas.webplayer { border: none; outline: none; - width: 800px; - height: 600px; -} - -/** - * Hack to make emscripten stop messing with the canvas size while in fullscreen. - * Foiled again! - */ -:fullscreen canvas.webplayer { - min-width: 100vw; - max-width: 100vw; - min-height: 100vh; - max-height: 100vh; } textarea { diff --git a/pkg/emscripten/libretro/libretro.js b/pkg/emscripten/libretro/libretro.js index a2fd96b8e5..8b46fbc030 100644 --- a/pkg/emscripten/libretro/libretro.js +++ b/pkg/emscripten/libretro/libretro.js @@ -9,7 +9,7 @@ var initializationCount = 0; var Module = { noInitialRun: true, - arguments: ["-v", "--menu"], + arguments: ["-v", "--menu", "-c", "/home/web_user/retroarch/userdata/retroarch.cfg"], encoder: new TextEncoder(), message_queue: [], @@ -203,9 +203,8 @@ function setupFileSystem(backend) { var xfs2 = new BrowserFS.FileSystem.XmlHttpRequest(".index-xhr", "assets/cores/"); console.log("WEBPLAYER: initializing filesystem: " + backend); + mfs.mount('/home/web_user/retroarch/bundle', xfs1); mfs.mount('/home/web_user/retroarch/userdata', afs); - - mfs.mount('/home/web_user/retroarch/', xfs1); mfs.mount('/home/web_user/retroarch/userdata/content/downloads', xfs2); BrowserFS.initialize(mfs); var BFS = new BrowserFS.EmscriptenFS(Module.FS, Module.PATH, Module.ERRNO_CODES); @@ -386,4 +385,4 @@ function loadCore(core) { console.error("Couldn't load script", err); throw err; }); -} \ No newline at end of file +} diff --git a/tasks/task_http_emscripten.c b/tasks/task_http_emscripten.c new file mode 100644 index 0000000000..b987594c76 --- /dev/null +++ b/tasks/task_http_emscripten.c @@ -0,0 +1,360 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2011-2017 - Daniel De Matteis + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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 RetroArch. + * If not, see . + */ + +#ifndef EMSCRIPTEN +#error "task_http_emscripten only makes sense in emscripten builds" +#endif + +#include +#include "verbosity.h" +#include + +#include +#include +#include +#include +#include + +#ifdef RARCH_INTERNAL +#include "../gfx/video_display_server.h" +#endif +#include "task_file_transfer.h" +#include "tasks_internal.h" + +struct http_handle +{ + int handle; + char connection_url[NAME_MAX_LENGTH]; + http_transfer_data_t *response; +}; + +typedef struct http_handle http_handle_t; + +static void task_http_transfer_handler(retro_task_t *task) +{ + http_handle_t *http = (http_handle_t*)task->state; + uint8_t flg = task_get_flags(task); + + if ((flg & RETRO_TASK_FLG_CANCELLED) > 0) + goto task_finished; + + if (http->response || (http->handle == -1)) + goto task_finished; + + return; +task_finished: + task_set_flags(task, RETRO_TASK_FLG_FINISHED, true); + if (http->response) + { + if ((flg & RETRO_TASK_FLG_CANCELLED) > 0) + { + string_list_free(http->response->headers); + free(http->response->data); + free(http->response); + http->response = NULL; + task_set_error(task, + strldup("Task cancelled.", sizeof("Task cancelled."))); + } + else + { + bool mute; + task_set_data(task, http->response); + mute = ((task->flags & RETRO_TASK_FLG_MUTE) > 0); + if (!mute && http->response->status >= 400) + task_set_error(task, strldup("Download failed.", + sizeof("Download failed."))); + } + } + free(http); +} + +static void http_transfer_progress_cb(retro_task_t *task) +{ + if (task) + video_display_server_set_window_progress(task->progress, + ((task->flags & RETRO_TASK_FLG_FINISHED) > 0)); +} + +static bool task_http_finder(retro_task_t *task, void *user_data) +{ + http_handle_t *http = NULL; + if (task && (task->handler == task_http_transfer_handler) && user_data) + if ((http = (http_handle_t*)task->state)) + return string_is_equal(http->connection_url, (const char*)user_data); + return false; +} +static void task_http_transfer_cleanup(retro_task_t *task) +{ + http_transfer_data_t* data = (http_transfer_data_t*)task_get_data(task); + if (data) + { + string_list_free(data->headers); + if (data->data) + free(data->data); + free(data); + } +} + +void wget_onload_cb(unsigned handle, void *t_ptr, void *data, unsigned len) { + retro_task_t *task = (retro_task_t *)t_ptr; + http_handle_t *http = (http_handle_t*)task->state; + http_transfer_data_t *resp; + if (!(resp = (http_transfer_data_t*)malloc(sizeof(*resp)))) { + http->handle = -1; + return; + } else { + resp->data = data; + resp->len = len; + resp->status = 200; + resp->headers = NULL; // sorry webdav + http->response = resp; + } +} + +void wget_onerror_cb(unsigned handle, void *t_ptr, int status, const char *err) { + retro_task_t *task = (retro_task_t *)t_ptr; + http_handle_t *http = (http_handle_t*)task->state; + bool mute = ((task->flags & RETRO_TASK_FLG_MUTE) > 0); + if (!mute) + task_set_error(task, strldup("Download failed.", + sizeof("Download failed."))); + http->handle = -1; +} + +void wget_onprogress_cb(unsigned handle, void *t_ptr, int pos, int tot) { + retro_task_t *task = (retro_task_t *)t_ptr; + if (tot == 0) + task_set_progress(task, -1); + else if (pos < (((size_t)-1) / 100)) + /* prefer multiply then divide for more accurate results */ + task_set_progress(task, (signed)(pos * 100 / tot)); + else + /* but invert the logic if it would cause an overflow */ + task_set_progress(task, MIN((signed)pos / (tot / 100), 100)); +} + +static void *task_push_http_transfer_generic( + const char *url, const char *method, + const char *data, const char *user_agent, + const char *headers, + bool mute, + retro_task_callback_t cb, void *user_data) +{ + retro_task_t *t = NULL; + http_handle_t *http = NULL; + int wget_handle = -1; + if (!url) + return NULL; + + if (!string_is_equal(method, "GET")) + { + /* POST requests usually mutate the server, so assume multiple calls are + * intended, even if they're duplicated. Additionally, they may differ + * only by the POST data, and task_http_finder doesn't look at that, so + * unique requests could be misclassified as duplicates. + */ + } + else + { + task_finder_data_t find_data; + find_data.func = task_http_finder; + find_data.userdata = (void*)url; + + /* Concurrent download of the same file is not allowed */ + if (task_queue_find(&find_data)) + return NULL; + } + + if (!(http = (http_handle_t*)malloc(sizeof(*http)))) + goto error; + + http->handle = -1; + http->response = NULL; + http->connection_url[0] = '\0'; + + strlcpy(http->connection_url, url, sizeof(http->connection_url)); + + if (!(t = task_init())) + goto error; + + + t->handler = task_http_transfer_handler; + t->state = http; + t->callback = cb; + t->progress_cb = http_transfer_progress_cb; + t->cleanup = task_http_transfer_cleanup; + t->user_data = user_data; + t->progress = -1; + if (mute) + t->flags |= RETRO_TASK_FLG_MUTE; + else + t->flags &= ~RETRO_TASK_FLG_MUTE; + + wget_handle = emscripten_async_wget2_data(url, method, data, t, false, wget_onload_cb, wget_onerror_cb, wget_onprogress_cb); + + http->handle = wget_handle; + + task_queue_push(t); + + return t; + +error: + if (http) + free(http); + if (t) + free(t); + return NULL; +} + + +void* task_push_http_transfer(const char *url, bool mute, + const char *type, + retro_task_callback_t cb, void *user_data) +{ + return task_push_http_transfer_generic(url, type ? type : "GET", NULL, NULL, NULL, mute, cb, user_data); +} + +void *task_push_webdav_stat(const char *url, bool mute, const char *headers, + retro_task_callback_t cb, void *user_data) +{ + RARCH_ERR("[http] response headers not supported, webdav won't work\n"); + return task_push_http_transfer_generic(url, "OPTIONS", NULL, NULL, headers, mute, cb, user_data); +} + +void* task_push_webdav_mkdir(const char *url, bool mute, + const char *headers, + retro_task_callback_t cb, void *user_data) +{ + RARCH_ERR("[http] response headers not supported, webdav won't work\n"); + return task_push_http_transfer_generic(url, "MKCOL", NULL, NULL, headers, mute, cb, user_data); +} + +void* task_push_webdav_put(const char *url, + const void *put_data, size_t len, bool mute, + const char *headers, retro_task_callback_t cb, void *user_data) +{ + char expect[1024]; /* TODO/FIXME - check size */ + size_t _len; + RARCH_ERR("[http] response headers not supported, webdav won't work\n"); + + _len = strlcpy(expect, "Expect: 100-continue\r\n", sizeof(expect)); + if (headers) + { + strlcpy(expect + _len, headers, sizeof(expect) - _len); + } + + return task_push_http_transfer_generic(url, "PUT", put_data, NULL, expect, mute, cb, user_data); +} + +void* task_push_webdav_delete(const char *url, bool mute, + const char *headers, + retro_task_callback_t cb, void *user_data) +{ + RARCH_ERR("[http] response headers not supported, webdav won't work\n"); + return task_push_http_transfer_generic(url, "DELETE", NULL, NULL, headers, mute, cb, user_data); +} + +void *task_push_webdav_move(const char *url, + const char *dest, bool mute, const char *headers, + retro_task_callback_t cb, void *user_data) +{ + size_t _len; + char dest_header[PATH_MAX_LENGTH + 512]; + RARCH_ERR("[http] response headers not supported, webdav won't work\n"); + + _len = strlcpy(dest_header, "Destination: ", sizeof(dest_header)); + _len += strlcpy(dest_header + _len, dest, sizeof(dest_header) - _len); + _len += strlcpy(dest_header + _len, "\r\n", sizeof(dest_header) - _len); + + if (headers) + strlcpy(dest_header + _len, headers, sizeof(dest_header) - _len); + + return task_push_http_transfer_generic(url, "MOVE", NULL, NULL, dest_header, mute, cb, user_data); +} + +void* task_push_http_transfer_file(const char* url, bool mute, + const char* type, + retro_task_callback_t cb, file_transfer_t* transfer_data) +{ + size_t _len; + const char *s = NULL; + char tmp[NAME_MAX_LENGTH] = ""; + retro_task_t *t = NULL; + + if (string_is_empty(url)) + return NULL; + + if (!(t = (retro_task_t*)task_push_http_transfer_generic( + /* should be using type but some callers now rely on type being ignored */ + url, "GET", + NULL, NULL, NULL, + mute, cb, transfer_data))) + return NULL; + + if (transfer_data) + s = transfer_data->path; + else + s = url; + + _len = strlcpy(tmp, msg_hash_to_str(MSG_DOWNLOADING), sizeof(tmp)); + tmp[ _len] = ' '; + tmp[++_len] = '\0'; + + if (string_ends_with_size(s, ".index", + strlen(s), STRLEN_CONST(".index"))) + s = msg_hash_to_str(MSG_INDEX_FILE); + + strlcpy(tmp + _len, s, sizeof(tmp) - _len); + + t->title = strdup(tmp); + return t; +} + +void* task_push_http_transfer_with_user_agent(const char *url, bool mute, + const char *type, const char *user_agent, + retro_task_callback_t cb, void *user_data) +{ + return task_push_http_transfer_generic(url, type ? type : "GET", NULL, user_agent, NULL, mute, cb, user_data); +} + +void* task_push_http_transfer_with_headers(const char *url, bool mute, + const char *type, const char *headers, + retro_task_callback_t cb, void *user_data) +{ + return task_push_http_transfer_generic(url, type ? type : "GET", NULL, NULL, headers, mute, cb, user_data); +} + +void* task_push_http_post_transfer(const char *url, + const char *post_data, bool mute, + const char *type, retro_task_callback_t cb, void *user_data) +{ + return task_push_http_transfer_generic(url, type ? type : "POST", post_data, NULL, NULL, mute, cb, user_data); +} + +void* task_push_http_post_transfer_with_user_agent(const char *url, + const char *post_data, bool mute, + const char *type, const char *user_agent, + retro_task_callback_t cb, void *user_data) +{ + return task_push_http_transfer_generic(url, type ? type : "POST", post_data, user_agent, NULL, mute, cb, user_data); +} + +void* task_push_http_post_transfer_with_headers(const char *url, + const char *post_data, bool mute, + const char *type, const char *headers, + retro_task_callback_t cb, void *user_data) +{ + return task_push_http_transfer_generic(url, type ? type : "POST", post_data, NULL, headers, mute, cb, user_data); +}