diff --git a/native/boxedwine/resize-host.c b/native/boxedwine/resize-host.c index bc9d321f..70f2a95d 100644 --- a/native/boxedwine/resize-host.c +++ b/native/boxedwine/resize-host.c @@ -2,6 +2,7 @@ #include #define SIZE_FILE L"D:\\boxedwine-size.txt" +#define WINDOW_SIZE_FILE L"D:\\boxedwine-window-size.txt" typedef struct { DWORD process_id; @@ -23,6 +24,30 @@ static BOOL parse_dimension(char **cursor, char *end, unsigned int *value) { return TRUE; } +static void append_number(char **cursor, unsigned int value) { + char digits[16]; + unsigned int length = 0; + do { + digits[length++] = (char)('0' + value % 10); + value /= 10; + } while (value); + while (length) *(*cursor)++ = digits[--length]; +} + +static void write_window_size(unsigned int width, unsigned int height) { + char buffer[40]; + char *cursor = buffer; + append_number(&cursor, width); + *cursor++ = ' '; + append_number(&cursor, height); + HANDLE file = CreateFileW(WINDOW_SIZE_FILE, GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) return; + DWORD written; + WriteFile(file, buffer, (DWORD)(cursor - buffer), &written, NULL); + CloseHandle(file); +} + static BOOL read_window_size(unsigned int *width, unsigned int *height, unsigned int *native_width, unsigned int *native_height) { @@ -76,6 +101,8 @@ static DWORD run(void) { WCHAR application_path[MAX_PATH]; unsigned int applied_width = 0; unsigned int applied_height = 0; + unsigned int reported_width = 0; + unsigned int reported_height = 0; int width_adjustment = 0; int height_adjustment = 0; BOOL calibrated = FALSE; @@ -99,12 +126,22 @@ static DWORD run(void) { unsigned int native_height; WindowSearch search = {process.dwProcessId, NULL, 0}; EnumWindows(find_process_window, (LPARAM)&search); - if (!search.window || - !read_window_size(&width, &height, &native_width, &native_height)) + if (!search.window) continue; + RECT bounds; + if (GetWindowRect(search.window, &bounds)) { + unsigned int current_width = (unsigned int)(bounds.right - bounds.left); + unsigned int current_height = (unsigned int)(bounds.bottom - bounds.top); + if (current_width != reported_width || + current_height != reported_height) { + write_window_size(current_width, current_height); + reported_width = current_width; + reported_height = current_height; + } + } + if (!read_window_size(&width, &height, &native_width, &native_height)) continue; if (width == applied_width && height == applied_height) continue; if (!calibrated) { - RECT bounds; if (!GetWindowRect(search.window, &bounds)) continue; width_adjustment = bounds.right - bounds.left - (int)native_width; height_adjustment = bounds.bottom - bounds.top - (int)native_height; diff --git a/site/apps/core/boxedwine-preload.js b/site/apps/core/boxedwine-preload.js new file mode 100644 index 00000000..7961583f --- /dev/null +++ b/site/apps/core/boxedwine-preload.js @@ -0,0 +1,87 @@ +const RUNTIME_ROOT = "vendor/boxedwine/26R1/"; +let preloadPromise; + +export const isConstrainedBoxedWineDevice = (hostWindow = window) => { + const connection = hostWindow.navigator.connection; + const mobileUserAgent = + hostWindow.navigator.userAgentData?.mobile === true || + /Android|iPad|iPhone|iPod|Mobile/i.test(hostWindow.navigator.userAgent); + return Boolean( + connection?.saveData || + /(?:^|-)2g$/.test(connection?.effectiveType || "") || + (Number.isFinite(hostWindow.navigator.deviceMemory) && + hostWindow.navigator.deviceMemory < 4) || + hostWindow.matchMedia?.("(max-width: 600px)").matches || + hostWindow.matchMedia?.("(pointer: coarse)").matches || + mobileUserAgent, + ); +}; + +const runtimeUrl = (path, hostWindow) => + new URL(`${RUNTIME_ROOT}${path}`, hostWindow.document.baseURI).href; + +export const preloadBoxedWineRuntime = async ({ + automatic = false, + hostWindow = window, +} = {}) => { + if (automatic && isConstrainedBoxedWineDevice(hostWindow)) return false; + if (preloadPromise) return preloadPromise; + + preloadPromise = hostWindow + .fetch(runtimeUrl("preload.json", hostWindow), { + cache: "force-cache", + credentials: "same-origin", + }) + .then(async (response) => { + if (!response.ok) + throw new Error("BoxedWine preload manifest unavailable"); + const manifest = await response.json(); + if ( + !Array.isArray(manifest.files) || + manifest.files.some( + (file) => + typeof file !== "string" || !/^[a-z0-9][a-z0-9.-]*$/i.test(file), + ) + ) { + throw new Error("Invalid BoxedWine preload manifest"); + } + await Promise.all( + manifest.files.map(async (file) => { + const assetResponse = await hostWindow.fetch( + runtimeUrl(file, hostWindow), + { + cache: "force-cache", + credentials: "same-origin", + }, + ); + if (!assetResponse.ok) { + throw new Error("BoxedWine preload asset unavailable"); + } + await assetResponse.arrayBuffer(); + }), + ); + return true; + }) + .catch((error) => { + preloadPromise = undefined; + throw error; + }); + return preloadPromise; +}; + +export const scheduleBoxedWinePreload = (hostWindow = window) => { + if (isConstrainedBoxedWineDevice(hostWindow)) return false; + const start = () => + preloadBoxedWineRuntime({ automatic: true, hostWindow }).catch(() => {}); + if (typeof hostWindow.requestIdleCallback === "function") { + hostWindow.requestIdleCallback(start, { timeout: 3000 }); + } else { + hostWindow.setTimeout(start, 1500); + } + return true; +}; + +window.XPBoxedWinePreload = Object.freeze({ + preload: () => preloadBoxedWineRuntime(), + schedule: () => scheduleBoxedWinePreload(), +}); diff --git a/site/apps/core/boxedwine-resize.js b/site/apps/core/boxedwine-resize.js index 6ec2cc5a..9a58eb09 100644 --- a/site/apps/core/boxedwine-resize.js +++ b/site/apps/core/boxedwine-resize.js @@ -19,6 +19,7 @@ export const installBoxedWineResizeBridge = (hostWindow, module) => { let runtimeInitialized = false; let windowSizeTimer = null; let previousWindowSize = ""; + let startupWindowReported = false; const reportWindowSize = () => { if (!runtimeInitialized || typeof module.FS?.readFile !== "function") @@ -31,6 +32,13 @@ export const installBoxedWineResizeBridge = (hostWindow, module) => { const match = /^(\d+) (\d+)$/.exec(text); if (!match) return; previousWindowSize = text; + if (!startupWindowReported) { + startupWindowReported = true; + hostWindow.BoxedWineStartup?.report("window-ready", { + width: Number(match[1]), + height: Number(match[2]), + }); + } hostWindow.parent.postMessage( { type: "boxedwine-window-size", diff --git a/site/apps/core/boxedwine.js b/site/apps/core/boxedwine.js index 6c19d5d4..88810034 100644 --- a/site/apps/core/boxedwine.js +++ b/site/apps/core/boxedwine.js @@ -1,4 +1,5 @@ const RUNTIME_ROOT = "vendor/boxedwine/26R1/"; +const ROOT_ARCHIVE = "xp-accessories"; const packageRoot = (packageId) => { const configuredRoot = window.ASTRO_GAME_ROOTS?.[packageId]; @@ -17,11 +18,14 @@ const runnerUrl = ({ const url = new URL(`${RUNTIME_ROOT}index.html`, document.baseURI); url.search = new URLSearchParams({ appRoot: new URL(packageRoot(packageId), document.baseURI).href, + root: ROOT_ARCHIVE, archive, executable, resolution, frameTop: String(frameTop), sound: String(sound), + cache: "false", + trace: "false", }); return url.href; }; @@ -52,6 +56,7 @@ export const mountBoxedWineApplication = ({ background = "#000", scaleOnly = false, onWindowSize, + onStartupMetric, nativeFrameWidth = 0, nativeFrameHeight = 0, }) => { @@ -133,10 +138,29 @@ export const mountBoxedWineApplication = ({ resizeObserver?.observe(host); frame.addEventListener("load", updateLayout); const handleWindowMessage = (event) => { - const { type, width, height } = event.data || {}; + const { type, width, height, stage, elapsed } = event.data || {}; if ( event.source !== frame.contentWindow || - event.origin !== new URL(frame.src).origin || + event.origin !== new URL(frame.src).origin + ) + return; + if ( + type === "boxedwine-startup" && + typeof stage === "string" && + Number.isFinite(elapsed) && + elapsed >= 0 + ) { + const metric = { ...event.data }; + delete metric.type; + host.dataset.boxedwineStartupStage = metric.stage; + host.dataset.boxedwineStartupElapsed = String(metric.elapsed); + onStartupMetric?.(metric); + host.dispatchEvent( + new window.CustomEvent("boxedwine-startup-metric", { detail: metric }), + ); + return; + } + if ( type !== "boxedwine-window-size" || !Number.isInteger(width) || !Number.isInteger(height) || diff --git a/site/apps/index.js b/site/apps/index.js index 25fc45ce..6f7b2d03 100644 --- a/site/apps/index.js +++ b/site/apps/index.js @@ -2,6 +2,7 @@ import { accessoryApplications } from "./catalog/accessories.js"; import { systemToolApplications } from "./catalog/system-tools.js"; import { systemApplications } from "./catalog/system-applications.js"; import { createApplicationRegistry } from "./core/registry.js"; +import "./core/boxedwine-preload.js"; import { paintApplication } from "./paint/index.js"; import { notepadApplication } from "./notepad/index.js"; import { winampApplication } from "./winamp/index.js"; diff --git a/site/iframe/freecell/SOURCES.json b/site/iframe/freecell/SOURCES.json index 9c3f055c..51464e91 100644 --- a/site/iframe/freecell/SOURCES.json +++ b/site/iframe/freecell/SOURCES.json @@ -5,8 +5,8 @@ "sourceSha256": "fd8c8d42c1581e8767217fe800bfc0d5649c0ad20d754c927d6c763e446d1927", "package": { "file": "xp-freecell.zip", - "bytes": 178720, - "sha256": "11262b2bd42a0da786a0ef8facf61c9395bea97768285da2529a27bef14c3252" + "bytes": 178938, + "sha256": "13575f808874364ef95b7693a9a2b9d84fbd7139654174cf1d360a9da2a8f2f5" }, "files": { "freecell.exe": { @@ -19,8 +19,8 @@ }, "resize-host.exe": { "source": "native/boxedwine/resize-host.c", - "build": "i686-w64-mingw32-gcc -Os -s -nostdlib -mwindows native/boxedwine/resize-host.c -lkernel32 -luser32 -o resize-host.exe", - "sha256": "adf6e0075ed512a40eaa06ec9f0389f1777e0b885153a4fc1b9eedf070426f5c" + "build": "SOURCE_DATE_EPOCH=0 i686-w64-mingw32-gcc -Os -s -nostdlib -mwindows -Wl,--no-insert-timestamp native/boxedwine/resize-host.c -lkernel32 -luser32 -o resize-host.exe", + "sha256": "deca3199da0d60a398e1ed1865c8a5c0f44516e24d11e863e0b52bd0c6065601" } } } diff --git a/site/iframe/freecell/xp-freecell.zip b/site/iframe/freecell/xp-freecell.zip index 6bddd386..61f76234 100644 Binary files a/site/iframe/freecell/xp-freecell.zip and b/site/iframe/freecell/xp-freecell.zip differ diff --git a/site/iframe/solitaire/SOURCES.json b/site/iframe/solitaire/SOURCES.json index ce0b2564..fdf8d3ac 100644 --- a/site/iframe/solitaire/SOURCES.json +++ b/site/iframe/solitaire/SOURCES.json @@ -5,8 +5,8 @@ "sourceSha256": "fd8c8d42c1581e8767217fe800bfc0d5649c0ad20d754c927d6c763e446d1927", "package": { "file": "xp-solitaire.zip", - "bytes": 178239, - "sha256": "d4ba31c630b08b478a60bee49630e6b4fc449ed0a168ca3d4a2bda1c89c4216c" + "bytes": 178457, + "sha256": "e452928237f2a8aeb92b8d5090b604b5d697d54ee2e92b2e5778d8d7b22d9ce1" }, "files": { "sol.exe": { @@ -19,8 +19,8 @@ }, "resize-host.exe": { "source": "native/boxedwine/resize-host.c", - "build": "i686-w64-mingw32-gcc -Os -s -nostdlib -mwindows native/boxedwine/resize-host.c -lkernel32 -luser32 -o resize-host.exe", - "sha256": "adf6e0075ed512a40eaa06ec9f0389f1777e0b885153a4fc1b9eedf070426f5c" + "build": "SOURCE_DATE_EPOCH=0 i686-w64-mingw32-gcc -Os -s -nostdlib -mwindows -Wl,--no-insert-timestamp native/boxedwine/resize-host.c -lkernel32 -luser32 -o resize-host.exe", + "sha256": "deca3199da0d60a398e1ed1865c8a5c0f44516e24d11e863e0b52bd0c6065601" } } } diff --git a/site/iframe/solitaire/xp-solitaire.zip b/site/iframe/solitaire/xp-solitaire.zip index 2ba1aaa2..ab5fef4b 100644 Binary files a/site/iframe/solitaire/xp-solitaire.zip and b/site/iframe/solitaire/xp-solitaire.zip differ diff --git a/site/iframe/spider-solitaire/SOURCES.json b/site/iframe/spider-solitaire/SOURCES.json index 62a1f84a..5ea98707 100644 --- a/site/iframe/spider-solitaire/SOURCES.json +++ b/site/iframe/spider-solitaire/SOURCES.json @@ -5,8 +5,8 @@ "sourceSha256": "fd8c8d42c1581e8767217fe800bfc0d5649c0ad20d754c927d6c763e446d1927", "package": { "file": "xp-spider-solitaire.zip", - "bytes": 271876, - "sha256": "a3a05d06ac7d6abd0680ba1ccdc28a2e7a0d27ca0e461fd80ae47fad7b37d847" + "bytes": 272094, + "sha256": "274230cc0219d579998bda3101784dad8a2126087903e127e6f746f1b9e8abb1" }, "files": { "spider.exe": { @@ -15,8 +15,8 @@ }, "resize-host.exe": { "source": "native/boxedwine/resize-host.c", - "build": "i686-w64-mingw32-gcc -Os -s -nostdlib -mwindows native/boxedwine/resize-host.c -lkernel32 -luser32 -o resize-host.exe", - "sha256": "adf6e0075ed512a40eaa06ec9f0389f1777e0b885153a4fc1b9eedf070426f5c" + "build": "SOURCE_DATE_EPOCH=0 i686-w64-mingw32-gcc -Os -s -nostdlib -mwindows -Wl,--no-insert-timestamp native/boxedwine/resize-host.c -lkernel32 -luser32 -o resize-host.exe", + "sha256": "deca3199da0d60a398e1ed1865c8a5c0f44516e24d11e863e0b52bd0c6065601" } } } diff --git a/site/iframe/spider-solitaire/xp-spider-solitaire.zip b/site/iframe/spider-solitaire/xp-spider-solitaire.zip index 78a24bc1..0c595c20 100644 Binary files a/site/iframe/spider-solitaire/xp-spider-solitaire.zip and b/site/iframe/spider-solitaire/xp-spider-solitaire.zip differ diff --git a/site/js/offline.js b/site/js/offline.js index 1d39b562..183b8613 100644 --- a/site/js/offline.js +++ b/site/js/offline.js @@ -139,6 +139,7 @@ let lifecycleListenersAttached = false; let updateRetryTimer = null; let bypassWorkerCdn = false; + let automaticTargetVersion = null; const versionedServiceWorkerUrl = (version) => { const separator = serviceWorkerUrl.includes("?") ? "&" : "?"; @@ -194,6 +195,7 @@ currentVersion, availableVersion: null, availableRevision: null, + updateEligibleAt: null, downloadBytes: cachedDownloadBytes, bundledGameBytes: null, downloadMetadataError: false, @@ -266,6 +268,9 @@ }); }; + const updateEligibleAt = (metadata) => + Date.parse(metadata.releasedAt) + metadata.stabilityDelayMs; + const markWaitingUpdate = async (worker, knownMetadata = null) => { if (!worker) return; let metadata = knownMetadata; @@ -276,6 +281,23 @@ scheduleUpdateRetry(); return false; } + const eligibleAt = updateEligibleAt(metadata); + if (environment.Date.now() < eligibleAt) { + if (automaticTargetVersion === metadata.version) { + automaticTargetVersion = null; + } + setState({ + phase: "update-pending", + updateReady: false, + workerState: "waiting", + availableVersion: metadata.version, + availableRevision: metadata.revision, + updateEligibleAt: eligibleAt, + downloadBytes: metadata.offlineBytes, + error: null, + }); + return false; + } const workerVersion = await requestWorkerVersion(worker); if (workerVersion !== metadata.version) { bypassWorkerCdn = true; @@ -285,6 +307,7 @@ workerState: "waiting", availableVersion: metadata.version, availableRevision: metadata.revision, + updateEligibleAt: eligibleAt, downloadBytes: metadata.offlineBytes, error: null, }); @@ -298,9 +321,14 @@ workerState: "waiting", availableVersion: metadata?.version || state.availableVersion, availableRevision: metadata?.revision || state.availableRevision, + updateEligibleAt: eligibleAt, downloadBytes: metadata?.offlineBytes || state.downloadBytes, error: null, }); + if (automaticTargetVersion === metadata.version) { + automaticTargetVersion = null; + await applyUpdate(); + } return true; }; @@ -445,6 +473,10 @@ if ( typeof metadata.version !== "string" || typeof metadata.revision !== "string" || + typeof metadata.releasedAt !== "string" || + !Number.isFinite(Date.parse(metadata.releasedAt)) || + !Number.isInteger(metadata.stabilityDelayMs) || + metadata.stabilityDelayMs < 0 || !Number.isFinite(metadata.offlineBytes) || metadata.offlineBytes <= 0 || !Number.isFinite(metadata.bundledGameBytes) || @@ -710,7 +742,7 @@ } }; - const checkForUpdates = async () => { + const checkForUpdates = async ({ applyAutomatically = false } = {}) => { if (checkPromise) return checkPromise; checkPromise = (async () => { const previousPhase = state.phase; @@ -718,15 +750,11 @@ try { const metadata = await fetchVersion(); rememberVersionMetadata(metadata); - await registerAndWait(metadata.version); - let waitingUpdateReady = false; - if (registration.waiting) { - waitingUpdateReady = await markWaitingUpdate( - registration.waiting, - metadata, - ); - } else if (registration.installing) { - trackInstallingWorker(registration.installing, true); + if ( + automaticTargetVersion && + automaticTargetVersion !== metadata.version + ) { + automaticTargetVersion = null; } const checkedAt = environment.Date.now(); storage.setItem(LAST_CHECKED_KEY, String(checkedAt)); @@ -737,6 +765,37 @@ metadata.version !== currentVersion || (activeWorkerVersion !== null && activeWorkerVersion !== metadata.version); + const eligibleAt = updateEligibleAt(metadata); + if (updateAvailable && checkedAt < eligibleAt) { + automaticTargetVersion = null; + setState({ + phase: "update-pending", + availableVersion: metadata.version, + availableRevision: metadata.revision, + lastChecked: checkedAt, + updateEligibleAt: eligibleAt, + updateReady: false, + error: null, + }); + return snapshot(); + } + if (applyAutomatically && updateAvailable) { + automaticTargetVersion = metadata.version; + } + await registerAndWait(metadata.version); + let waitingUpdateReady = false; + if (registration.waiting) { + waitingUpdateReady = await markWaitingUpdate( + registration.waiting, + metadata, + ); + } else if (registration.installing) { + trackInstallingWorker(registration.installing, true); + } + if (reloadWhenControlled) { + setState({ lastChecked: checkedAt }); + return snapshot(); + } if ( activeWorkerVersion !== null && activeWorkerVersion !== metadata.version && @@ -762,6 +821,7 @@ availableVersion: updateAvailable ? metadata.version : null, availableRevision: updateAvailable ? metadata.revision : null, lastChecked: checkedAt, + updateEligibleAt: updateAvailable ? eligibleAt : null, updateReady: updateAvailable && waitingUpdateReady, workerState: registration?.waiting ? "waiting" @@ -875,7 +935,7 @@ await navigatorObject.serviceWorker?.getRegistration?.("/"); if (existingRegistration) trackRegistration(existingRegistration); try { - await checkForUpdates(); + await checkForUpdates({ applyAutomatically: true }); } catch (error) { if (!existingRegistration) throw error; setState({ phase: "ready", workerState: "active", error: null }); diff --git a/site/js/shell/session.js b/site/js/shell/session.js index 46f9df48..8b6354a0 100644 --- a/site/js/shell/session.js +++ b/site/js/shell/session.js @@ -194,6 +194,7 @@ const login = (playSound = true) => { clearTimeout(bootTimeout); loggedIn = true; showDesktop(); + window.XPBoxedWinePreload?.schedule(); applyDisplaySettings(getDisplaySettings()); applyStartMenuStyle(getStartMenuStyle(), false); applyFocusVolumes(); @@ -217,7 +218,6 @@ const login = (playSound = true) => { openLinkedGame(gameId); } } - maybePromptForUpdate(offlineManager.getSnapshot()); scheduleScreenSaver(); })().finally(() => { loginPromise = null; diff --git a/site/js/shell/start-menu.js b/site/js/shell/start-menu.js index da5cb7f2..f825781e 100644 --- a/site/js/shell/start-menu.js +++ b/site/js/shell/start-menu.js @@ -1058,6 +1058,9 @@ const createProgramMenuItem = (definition, depth) => { arrow.textContent = "▶"; item.appendChild(arrow); const open = (focusFirst = false) => { + if (["accessories", "games"].includes(definition.id)) { + window.XPBoxedWinePreload?.preload().catch(() => {}); + } openProgramSubmenu(definition.children, item, depth + 1); if (focusFirst) document diff --git a/site/js/shell/system-tray.js b/site/js/shell/system-tray.js index 46fe065b..22c13eb3 100644 --- a/site/js/shell/system-tray.js +++ b/site/js/shell/system-tray.js @@ -134,7 +134,6 @@ const openNetworkStatus = () => { }; let offlineManagerInitialized = false; -let promptedUpdateVersion = null; const offlineStatusText = (state) => { const messages = { @@ -146,8 +145,9 @@ const offlineStatusText = (state) => { "update-available": state.enabled ? "An update is downloading..." : "An update is available.", + "update-pending": "A newer update is waiting for its stability delay.", "update-ready": - "An update is ready. Restart Astro Flash Collection to apply it.", + "An update is ready and will install automatically on the next load.", "repair-required": "The installed update is incomplete. Repair the system files.", applying: "Applying the update...", @@ -172,34 +172,11 @@ const projectStorageText = (state) => { const formatProjectState = (value) => value ? `${value.charAt(0).toUpperCase()}${value.slice(1)}` : "Unavailable"; -const maybePromptForUpdate = (state) => { - if ( - !loggedIn || - !state.updateReady || - !state.availableVersion || - promptedUpdateVersion === state.availableVersion - ) { - return; - } - promptedUpdateVersion = state.availableVersion; - XPDialogs.confirm( - `Astro Flash Collection ${state.availableVersion} is ready.\nRestart now to apply the update?`, - "Astro Flash Update", - "info", - ).then((accepted) => { - if (!accepted) return; - offlineManager.applyUpdate().catch((error) => { - XPDialogs.alert(error.message, "Astro Flash Update", "error"); - }); - }); -}; - const initializeOfflineMode = () => { if (offlineManagerInitialized || window.ASTRO_DEV) { return offlineManagerInitialization; } offlineManagerInitialized = true; - offlineManager.subscribe(maybePromptForUpdate); offlineManagerInitialization = offlineManager.initialize().catch((error) => { console.error("Offline mode initialization failed:", error); throw error; @@ -317,7 +294,6 @@ const wireProjectSettings = (win) => {

-
@@ -358,7 +334,6 @@ const wireProjectSettings = (win) => { "[data-project-game-progress]", ); const checkButton = content.querySelector('[data-project-action="check"]'); - const applyButton = content.querySelector('[data-project-action="apply"]'); const repairButton = content.querySelector('[data-project-action="repair"]'); const downloadAllGamesButton = content.querySelector( '[data-project-action="download-all-games"]', @@ -601,13 +576,15 @@ const wireProjectSettings = (win) => { updateStatus.textContent = state.phase === "checking" ? "Checking for updates..." - : state.updateReady - ? `Astro Flash Collection ${state.availableVersion || "update"} is ready to install.` - : state.availableVersion - ? `Astro Flash Collection ${state.availableVersion} is available.` - : state.lastChecked - ? "Astro Flash Collection is up to date." - : "Updates have not been checked yet."; + : state.phase === "update-pending" + ? `Astro Flash Collection ${state.availableVersion} will become eligible after ${formatUpdateCheckTime(state.updateEligibleAt)} if no newer update is released.` + : state.updateReady + ? `Astro Flash Collection ${state.availableVersion || "update"} will install automatically the next time you load it.` + : state.availableVersion + ? `Astro Flash Collection ${state.availableVersion} is available.` + : state.lastChecked + ? "Astro Flash Collection is up to date." + : "Updates have not been checked yet."; if (state.error) { updateStatus.textContent = state.error; } @@ -625,10 +602,6 @@ const wireProjectSettings = (win) => { gameDownloadProgress.removeAttribute("value"); } checkButton.disabled = !state.online || transientPhases.has(state.phase); - applyButton.hidden = !state.availableVersion; - applyButton.disabled = state.enabled - ? !state.updateReady - : transientPhases.has(state.phase); repairButton.disabled = !state.online || transientPhases.has(state.phase); const gameBusy = ["downloading", "removing"].includes(state.gamePhase); downloadAllGamesButton.disabled = @@ -668,11 +641,6 @@ const wireProjectSettings = (win) => { checkButton.addEventListener("click", () => { offlineManager.checkForUpdates().catch(() => {}); }); - applyButton.addEventListener("click", () => { - offlineManager.applyUpdate().catch((error) => { - updateStatus.textContent = error.message; - }); - }); repairButton.addEventListener("click", async () => { const accepted = await XPDialogs.confirm( "Clear and download the Windows XP system files again?", diff --git a/site/vendor/boxedwine/26R1/BOXEDWINE-FILE-TRACE.patch b/site/vendor/boxedwine/26R1/BOXEDWINE-FILE-TRACE.patch new file mode 100644 index 00000000..45d1caec --- /dev/null +++ b/site/vendor/boxedwine/26R1/BOXEDWINE-FILE-TRACE.patch @@ -0,0 +1,166 @@ +diff --git a/include/ksystem.h b/include/ksystem.h +index 257f620..dd7e5ce 100644 +--- a/include/ksystem.h ++++ b/include/ksystem.h +@@ -105,6 +105,7 @@ public: + static bool disableHideCursor; + static bool forceRelativeMouse; + static bool cacheReads; ++ static bool traceReads; + static bool useF64; + static U32 pageSize; + static bool canJitUse4KPage; +diff --git a/project/emscripten/makefile b/project/emscripten/makefile +index dd4c05f..0d07f8a 100644 +--- a/project/emscripten/makefile ++++ b/project/emscripten/makefile +@@ -110,7 +110,7 @@ LDFLAGS = -s FORCE_FILESYSTEM -lidbfs.js -s USE_SDL=2 -s USE_ZLIB=1 -fwasm-ex + # $(CC) $(TEST_OBJS) -o $@ $(LDFLAGS) + + $(BUILD_DIR)/$(TARGET_EXEC): $(ALL) +- @$(CC) $(ALL) -o $@ $(LDFLAGS) ++ @$(CXX) $(ALL) -o $@ $(LDFLAGS) + + # assembly + $(BUILD_DIR)/%.s.o: %.s +diff --git a/source/io/fsfilenode.cpp b/source/io/fsfilenode.cpp +index 2cf9d94..13fdc87 100644 +--- a/source/io/fsfilenode.cpp ++++ b/source/io/fsfilenode.cpp +@@ -17,6 +17,9 @@ + */ + + #include "boxedwine.h" ++#ifdef __EMSCRIPTEN__ ++#include ++#endif + + #include + #include +@@ -217,6 +220,16 @@ FsOpenNode* FsFileNode::open(U32 flags) { + if (KSystem::cacheReads && this->zipNode) { + ensurePathIsLocal(false); + } ++#elif defined(BOXEDWINE_ZLIB) && defined(__EMSCRIPTEN__) ++ if (KSystem::traceReads && this->zipNode) { ++ EM_ASM({ ++ console.log("BOXEDWINE_FILE:" + UTF8ToString($0)); ++ window.parent.postMessage({ ++ type: "boxedwine-file-read", ++ path: UTF8ToString($0) ++ }, window.location.origin); ++ }, this->path.c_str()); ++ } + #endif + } else { + if ((flags & K_O_ACCMODE)==K_O_WRONLY) { +diff --git a/source/io/fszipnode.h b/source/io/fszipnode.h +index 7492198..b682b4f 100644 +--- a/source/io/fszipnode.h ++++ b/source/io/fszipnode.h +@@ -35,6 +35,9 @@ public: + bool moveToFileSystem(std::shared_ptr node); + + std::weak_ptr fsZip; ++#ifdef __EMSCRIPTEN__ ++ std::shared_ptr> cachedData; ++#endif + private: + fsZipInfo zipInfo; + }; +diff --git a/source/io/fszipopennode.cpp b/source/io/fszipopennode.cpp +index f6f062f..ddae473 100644 +--- a/source/io/fszipopennode.cpp ++++ b/source/io/fszipopennode.cpp +@@ -95,6 +95,36 @@ U32 FsZipOpenNode::readNative(U8* buffer, U32 len) { + BOXEDWINE_CRITICAL_SECTION_WITH_MUTEX(*getReadMutex()); + std::shared_ptr fsZip = zipNode->fsZip.lock(); + if (fsZip) { ++#ifdef __EMSCRIPTEN__ ++ if (KSystem::cacheReads) { ++ if (!zipNode->cachedData) { ++ auto cachedData = std::make_shared>(); ++ cachedData->resize(this->node->length()); ++ fsZip->setupZipRead(this->offset, 0); ++ U32 total = 0; ++ while (total < cachedData->size()) { ++ int read = unzReadCurrentFile( ++ fsZip->zipfile, ++ cachedData->data() + total, ++ cachedData->size() - total); ++ if (read <= 0) { ++ break; ++ } ++ total += read; ++ } ++ cachedData->resize(total); ++ zipNode->cachedData = cachedData; ++ } ++ if (this->pos < (S64)zipNode->cachedData->size()) { ++ result = (U32)std::min( ++ len, ++ zipNode->cachedData->size() - (U64)this->pos); ++ memcpy(buffer, zipNode->cachedData->data() + this->pos, result); ++ this->pos += result; ++ } ++ return result; ++ } ++#endif + fsZip->setupZipRead(this->offset, this->pos); + result = unzReadCurrentFile(fsZip->zipfile, buffer, len); + this->pos += result; +diff --git a/source/kernel/ksystem.cpp b/source/kernel/ksystem.cpp +index 57ba95e..4315347 100644 +--- a/source/kernel/ksystem.cpp ++++ b/source/kernel/ksystem.cpp +@@ -63,6 +63,7 @@ U32 KSystem::wineMajorVersion; + bool KSystem::disableHideCursor = false; + bool KSystem::forceRelativeMouse = false; + bool KSystem::cacheReads = false; ++bool KSystem::traceReads = false; + BString KSystem::showWindowTimestamp; + U32 KSystem::pageSize = 4096; + bool KSystem::canJitUse4KPage = false; +diff --git a/source/sdl/startupArgs.cpp b/source/sdl/startupArgs.cpp +index e64e39a..ae720d1 100644 +--- a/source/sdl/startupArgs.cpp ++++ b/source/sdl/startupArgs.cpp +@@ -269,6 +269,9 @@ std::vector StartUpArgs::buildArgs() { + if (this->cacheReads) { + args.push_back(B("-cacheReads")); + } ++ if (this->traceReads) { ++ args.push_back(B("-traceReads")); ++ } + for (auto& a : this->args) { + args.push_back(a); + } +@@ -286,6 +289,7 @@ bool StartUpArgs::apply() { + KSystem::disableHideCursor = this->disableHideCursor; + KSystem::forceRelativeMouse = this->forceRelativeMouse; + KSystem::cacheReads = this->cacheReads; ++ KSystem::traceReads = this->traceReads; + KSystem::pentiumLevel = this->pentiumLevel; + KSystem::pollRate = this->pollRate; + if (KSystem::pollRate < 0) { +@@ -827,6 +831,8 @@ bool StartUpArgs::parseStartupArgs(int argc, const char **argv) { + this->forceRelativeMouse = true; + } else if (!strcmp(argv[i], "-cacheReads")) { + this->cacheReads = true; ++ } else if (!strcmp(argv[i], "-traceReads")) { ++ this->traceReads = true; + } + else if (!strcmp(argv[i], "-dxvk")) { + BString dxvk; +diff --git a/source/sdl/startupArgs.h b/source/sdl/startupArgs.h +index 4740064..69296f0 100644 +--- a/source/sdl/startupArgs.h ++++ b/source/sdl/startupArgs.h +@@ -115,6 +115,7 @@ public: + bool disableHideCursor = false; + bool forceRelativeMouse = false; + bool cacheReads = false; ++ bool traceReads = false; + + private: + bool workingDirSet = false; diff --git a/site/vendor/boxedwine/26R1/BOXEDWINE-SOURCE.txt b/site/vendor/boxedwine/26R1/BOXEDWINE-SOURCE.txt index 69c7a1f8..cb3f35f9 100644 --- a/site/vendor/boxedwine/26R1/BOXEDWINE-SOURCE.txt +++ b/site/vendor/boxedwine/26R1/BOXEDWINE-SOURCE.txt @@ -3,7 +3,21 @@ Commit: d7d5a1421bd781a81cbdf8f222cced11a7ebd76e License: GNU General Public License version 2 Complete corresponding source: https://github.com/danoon2/Boxedwine/tree/26R1.0 Local resize patch: BOXEDWINE-RESIZE.patch +Local Web cache and file-read trace patch: BOXEDWINE-FILE-TRACE.patch +Compiler: Emscripten 6.0.6 +Resource-safe build command: +make -j1 BUILD_DIR=Build/Release SHELL_FILE=shell.html EXTRA_CPP_FLAGS= +Normalize the generated JavaScript before verification: +perl -pi -e 's/[ \t]+$//' Build/Release/boxedwine.js +Before applying BOXEDWINE-FILE-TRACE.patch, normalize fsfilenode.cpp to LF: +perl -pi -e 's/\r$//' source/io/fsfilenode.cpp Official release: https://github.com/danoon2/Boxedwine/releases/tag/26R1.0 Official Web runtime: https://boxedwine.org/v/26R1/wine11/st/ +The full boxedwine.zip archive is the verified build input. The deployed +xp-accessories.zip archive contains the shared Wine prefix and the runtime files +observed while Calculator, FreeCell, Solitaire, and Spider Solitaire start and +operate. Rebuild it with: +bun tools/build-boxedwine-xp-filesystem.ts + The adjacent LICENSE-BOXEDWINE.txt file contains the BoxedWine license. diff --git a/site/vendor/boxedwine/26R1/SOURCES.json b/site/vendor/boxedwine/26R1/SOURCES.json index 2c0520ad..f012bd16 100644 --- a/site/vendor/boxedwine/26R1/SOURCES.json +++ b/site/vendor/boxedwine/26R1/SOURCES.json @@ -6,10 +6,23 @@ "releaseUrl": "https://github.com/danoon2/Boxedwine/releases/tag/26R1.0", "sourceUrl": "https://github.com/danoon2/Boxedwine/tree/26R1.0", "runtimeUrl": "https://boxedwine.org/v/26R1/wine11/st/", + "build": { + "compiler": "Emscripten 6.0.6", + "command": "make -j1 BUILD_DIR=Build/Release SHELL_FILE=shell.html EXTRA_CPP_FLAGS=", + "normalizeCommand": "perl -pi -e 's/[ \\t]+$//' Build/Release/boxedwine.js", + "patches": [ + "BOXEDWINE-RESIZE.patch", + "BOXEDWINE-FILE-TRACE.patch" + ], + "filesystemCommand": "bun tools/build-boxedwine-xp-filesystem.ts" + }, "files": { - "boxedwine-shell.js": "4eb37b1fe78c81451a22bc67485ac5e151b243912775596299b6736cd2c1678c", - "boxedwine.js": "4a5abecf3ab042dfbd62b4516edcb2a4a0ea660a0d8a6704545aca02e10379dd", - "boxedwine.wasm": "68e064fc1b71af0b9283725ec9fb38b8f461ee7b59e2082c2ea4883c96be7ade", - "boxedwine.zip": "5d251b6d53cad1c31de254c5285603d193f91bc3129b9c9ba39c4cc65a24edb3" + "boxedwine-shell.js": "ee18b4cf9812c5dd7d16b2708d9c33080cb1dc16dc757f79a2f11ac6b7802490", + "boxedwine-startup.js": "276ca5a89cfc69b46b34051207bbe7590b7812fe30fd53cf01b9e2b3a1057aa0", + "boxedwine.js": "fa83af03cc7abbee1e155068dfda2a3920e9a441d0039221cfb2c1bb2620b552", + "boxedwine.wasm": "1dc9aac81652bc2ee6da8fc358d77e57059a8149520e15724cd8589d5bfa49bc", + "boxedwine.zip": "5d251b6d53cad1c31de254c5285603d193f91bc3129b9c9ba39c4cc65a24edb3", + "xp-accessories-files.json": "a7ce518544382d5a44d20b9139c736152dfca9fadb54ccc6fb09c13a11664a5a", + "xp-accessories.zip": "02f703da02afef88579c3b5fe376afa915443f6b8566aa7f69568eb5814c7ffa" } } diff --git a/site/vendor/boxedwine/26R1/boxedwine-shell.js b/site/vendor/boxedwine/26R1/boxedwine-shell.js index 224b26d8..a3402a07 100644 --- a/site/vendor/boxedwine/26R1/boxedwine-shell.js +++ b/site/vendor/boxedwine/26R1/boxedwine-shell.js @@ -18,7 +18,7 @@ Config.locateOverlayBaseUrl = ""; Config.urlParams = ""; Config.storageMode = STORAGE_INDEXED_DB; - Config.persist_d_drive = true; + Config.persist_d_drive = false; Config.showUploadDownload = false; Config.WorkingDir = ""; Config.loadDesktop = false; @@ -331,25 +331,16 @@ } return contents; } - function loadFile(pathPrefix, filename, callback) { - fetch(pathPrefix + filename, { method: 'GET' }).then(function(response) { - if (response.status === 200) { - response.arrayBuffer().then(function(buffer) { - let arr = new Uint8Array(buffer); - callback(arr); - }); - } else { - console.log('Unable to load:' + filename + ' error:' + response.status); - } - }); - } - function buildAppFileSystem(callback) { + function loadFile(pathPrefix, filename) { + return BoxedWineStartup.load(pathPrefix + filename, filename); + } + function buildAppFileSystem() { if(Config.appPayload.length > 0){ let uint8Array = getBase64Data(Config.appPayload); createFile("/", Config.payloadZipFile, uint8Array); - callback(); + return Promise.resolve(); }else if(Config.appZipFile.length > 0){ - loadFile(Config.locateAppBaseUrl, Config.appZipFile, (uint8Array) => { + return loadFile(Config.locateAppBaseUrl, Config.appZipFile).then((uint8Array) => { if (Config.Program.length > 0) { let zipEntries = getZipEntries(uint8Array); let folder = Config.appZipFile.toLowerCase().endsWith('.zip') ? @@ -361,50 +352,40 @@ } } createFile("/", Config.appZipFile, uint8Array); - callback(); }); }else{ - callback(); + return Promise.resolve(); } } - function buildExtraFileSystems(callback) { - let extraFSs = []; + function buildExtraFileSystems() { if(Config.extraPayload.length > 0){ let uint8Array = getBase64Data(Config.extraPayload); createFile("/", "overlay.zip", uint8Array); - callback(); + return Promise.resolve(); }else if(Config.extraZipFiles.length > 0){ - for(let i = 0; i < Config.extraZipFiles.length; i++) { - loadFile(Config.locateOverlayBaseUrl, Config.extraZipFiles[i], (uint8Array) => { - createFile("/", Config.extraZipFiles[i], uint8Array); - extraFSs.push(Config.extraZipFiles[i]); - if(extraFSs.length == Config.extraZipFiles.length) { - callback(); - } - }); - } + return Promise.all(Config.extraZipFiles.map((filename) => + loadFile(Config.locateOverlayBaseUrl, filename).then((uint8Array) => + createFile("/", filename, uint8Array)))); }else{ - callback(); + return Promise.resolve(); } } - function initBrowserFilesystem(callback) { + function initBrowserFilesystem() { console.log("Use Storage mode: "+Config.storageMode); FS.mkdir(ROOT); FS.mkdir(Config.d_drive); if (Config.storageMode == STORAGE_INDEXED_DB) { FS.mount(IDBFS, {autoPersist: true}, ROOT); - if (Config.persist_d_drive) { - FS.mount(IDBFS, {autoPersist: true}, Config.d_drive); - } - FS.syncfs(true, function (err) { + return new Promise((resolve) => FS.syncfs(true, function (err) { if (err) { console.log('unable to sync folder: ' + ROOT); - } else { - callback(); } - }); + BoxedWineStartup.report("storage-ready"); + resolve(); + })); } else { - callback(); + BoxedWineStartup.report("storage-ready"); + return Promise.resolve(); } } function buildBrowserFileSystem() { @@ -437,6 +418,7 @@ } function startEmulator() { isRunning = true; + BoxedWineStartup.report("emulator-start"); document.getElementById('startbtn').style.display = 'none'; document.getElementById('sound-checkbox').style.display = 'none'; @@ -458,19 +440,22 @@ }); } Module["addRunDependency"]("setupBoxedWine"); - initBrowserFilesystem(() => { - spinnerElement.style.display = ''; - spinnerElement.hidden = false; - - buildExtraFileSystems(() => { - buildAppFileSystem(() => { - loadFile(Config.locateRootBaseUrl, Config.rootZipFile, (rootZipfileBytes) => { - createFile("/", Config.rootZipFile, rootZipfileBytes); - buildBrowserFileSystem(); - }); - }); - }); - }); + spinnerElement.style.display = ''; + spinnerElement.hidden = false; + const storageReady = initBrowserFilesystem(); + const appReady = buildAppFileSystem(); + const extrasReady = buildExtraFileSystems(); + const rootReady = loadFile(Config.locateRootBaseUrl, Config.rootZipFile); + Promise.all([storageReady, appReady, extrasReady, rootReady]) + .then(([, , , rootZipfileBytes]) => { + createFile("/", Config.rootZipFile, rootZipfileBytes); + BoxedWineStartup.report("filesystem-ready"); + buildBrowserFileSystem(); + }) + .catch((error) => { + console.error(error); + Module.setStatus(error.message); + }); } function getEntriesAsPromise(item, exeFiles, allFiles, firstCall) { return new Promise((resolve, reject) => { @@ -562,6 +547,12 @@ }, false); function getEmulatorParams() { let params = ["-root", ROOT]; + if (getParameter("cache") == "true") { + params.push("-cacheReads"); + } + if (getParameter("trace") == "true") { + params.push("-traceReads"); + } params.push("-zip"); params.push(Config.rootZipFile); diff --git a/site/vendor/boxedwine/26R1/boxedwine-startup.js b/site/vendor/boxedwine/26R1/boxedwine-startup.js new file mode 100644 index 00000000..6e4466f6 --- /dev/null +++ b/site/vendor/boxedwine/26R1/boxedwine-startup.js @@ -0,0 +1,46 @@ +(function installBoxedWineStartup(global) { + "use strict"; + + const now = () => global.performance?.now?.() ?? Date.now(); + const startedAt = now(); + const requests = new Map(); + const metrics = []; + + const report = (stage, detail = {}) => { + const currentTime = now(); + const message = { + type: "boxedwine-startup", + stage, + elapsed: Math.max(0, Math.round(currentTime - startedAt)), + ...detail, + }; + metrics.push(message); + global.parent?.postMessage(message, global.location.origin); + return message; + }; + + const load = (url, name) => { + if (requests.has(url)) return requests.get(url); + report("download-start", { name }); + const request = global + .fetch(url, { cache: "force-cache", credentials: "same-origin" }) + .then((response) => { + if (!response.ok) throw new Error(`Unable to load ${name}`); + return response.arrayBuffer(); + }) + .then((buffer) => { + report("download-ready", { name, bytes: buffer.byteLength }); + return new Uint8Array(buffer); + }); + requests.set(url, request); + void request + .finally(() => { + if (requests.get(url) === request) requests.delete(url); + }) + .catch(() => {}); + return request; + }; + + global.BoxedWineStartup = Object.freeze({ load, metrics, report }); + report("runner-ready"); +})(window); diff --git a/site/vendor/boxedwine/26R1/boxedwine.js b/site/vendor/boxedwine/26R1/boxedwine.js index 0e6b2e7f..2a92d987 100644 --- a/site/vendor/boxedwine/26R1/boxedwine.js +++ b/site/vendor/boxedwine/26R1/boxedwine.js @@ -494,7 +494,7 @@ function updateMemoryViews() { HEAPF32 = new Float32Array(b); HEAPF64 = new Float64Array(b); HEAP64 = new BigInt64Array(b); - + } // include: memoryprofiler.js @@ -802,11 +802,11 @@ async function createWasm() { } }; - + var UTF8Decoder = globalThis.TextDecoder && new TextDecoder(); - - + + /** * heapOrArray is either a regular array, or a JavaScript typed array view. * @param {number} idx @@ -824,8 +824,8 @@ async function createWasm() { while (heapOrArray[idx] && !(idx >= maxIdx)) ++idx; return idx; }; - - + + /** * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given * array that contains uint8 values, returns a copy of that string as a @@ -837,9 +837,9 @@ async function createWasm() { * @return {string} */ var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead, ignoreNul) => { - + var endPtr = findStringEnd(heapOrArray, idx, maxBytesToRead, ignoreNul); - + // When using conditional TextDecoder, skip it for short strings as the overhead of the native call is not worth it. if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); @@ -861,7 +861,7 @@ async function createWasm() { if ((u0 & 0xF8) != 0xF0) warnOnce(`Invalid UTF-8 leading byte ${ptrToString(u0)} encountered when deserializing a UTF-8 string in wasm memory to a JS string!`); u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); } - + if (u0 < 0x10000) { str += String.fromCharCode(u0); } else { @@ -871,10 +871,10 @@ async function createWasm() { } return str; }; - + /** @type {!Uint8Array} */ var HEAPU8; - + /** * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the * emscripten HEAP, returns a copy of that string as a Javascript String object. @@ -901,11 +901,11 @@ async function createWasm() { var nodeCrypto = require('node:crypto'); return (view) => (nodeCrypto.randomFillSync(view), 0); } - + return (view) => (crypto.getRandomValues(view), 0); }; var randomFill = (view) => (randomFill = initRandomFill())(view); - + var PATH = { isAbs:(path) => path.charAt(0) === '/', splitPath:(filename) => { @@ -1105,7 +1105,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var BUFSIZE = 256; var buf = Buffer.alloc(BUFSIZE); var bytesRead = 0; - + // For some reason we must suppress a closure warning here, even though // fd definitely exists on process.stdin, and is even the proper way to // get the fd of stdin, @@ -1114,7 +1114,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // so it is related to the surrounding code in some unclear manner. /** @suppress {missingProperties} */ var fd = process.stdin.fd; - + try { bytesRead = fs.readSync(fd, buf, 0, BUFSIZE); } catch(e) { @@ -1124,7 +1124,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (e.toString().includes('EOF')) bytesRead = 0; else throw e; } - + if (bytesRead > 0) { result = buf.slice(0, bytesRead).toString('utf-8'); } @@ -1285,10 +1285,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, }, }; - - + + var zeroMemory = (ptr, size) => HEAPU8.fill(0, ptr, ptr + size); - + var alignMemory = (size, alignment) => { assert(alignment, 'alignment argument is required'); return Math.ceil(size / alignment) * alignment; @@ -1299,7 +1299,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (ptr) zeroMemory(ptr, size); return ptr; }; - + var MEMFS = { ops_table:null, mount(mount) { @@ -1516,11 +1516,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, write(stream, buffer, offset, length, position, canOwn) { assert(buffer.subarray, 'FS.write expects a TypedArray'); - + if (!length) return 0; var node = stream.node; node.mtime = node.ctime = Date.now(); - + if (canOwn) { assert(!position, 'canOwn must imply no weird position inside the file'); node.contents = buffer.subarray(offset, offset + length); @@ -1590,7 +1590,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, }, }; - + var FS_modeStringToFlags = (str) => { if (typeof str != 'string') return str; var flagModes = { @@ -1607,7 +1607,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return flags; }; - + var FS_fileDataToTypedArray = (data) => { if (typeof data == 'string') { data = intArrayFromString(data, true); @@ -1617,17 +1617,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return data; }; - + var FS_getMode = (canRead, canWrite) => { var mode = 0; if (canRead) mode |= 292 | 73; if (canWrite) mode |= 146; return mode; }; - - - - + + + + var IDBFS = { dbs:{ }, @@ -1650,7 +1650,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { IDBFS.onAutoPersistStateChanged?.(true); IDBFS.syncfs(mount, /*populate:*/false, onPersistComplete); } - + if (!mount.idbPersistState) { // Programs typically write/copy/move multiple files in the in-memory // filesystem within a single app frame, so when a filesystem sync @@ -1685,14 +1685,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { node.memfs_stream_ops = node.stream_ops; // Clone stream_ops to inject write tracking node.stream_ops = {...node.stream_ops}; - + // Track all file writes node.stream_ops.write = (stream, buffer, offset, length, position, canOwn) => { // This file has been modified, we must persist IndexedDB when this file closes stream.node.isModified = true; return node.memfs_stream_ops.write(stream, buffer, offset, length, position, canOwn); }; - + // Persist IndexedDB on file close node.stream_ops.close = (stream) => { var n = stream.node; @@ -1702,10 +1702,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } if (n.memfs_stream_ops.close) return n.memfs_stream_ops.close(stream); }; - + // Persist the node we just created to IndexedDB IDBFS.queuePersist(mnt.mount); - + return node; }; // Also kick off persisting the filesystem on other operations that modify the filesystem. @@ -1719,13 +1719,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { syncfs:(mount, populate, callback) => { IDBFS.getLocalSet(mount, (err, local) => { if (err) return callback(err); - + IDBFS.getRemoteSet(mount, (err, remote) => { if (err) return callback(err); - + var src = populate ? remote : local; var dst = populate ? local : remote; - + IDBFS.reconcile(src, dst, callback); }); }); @@ -1742,7 +1742,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (db) { return callback(null, db); } - + var req; try { req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION); @@ -1755,22 +1755,22 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { req.onupgradeneeded = (e) => { var db = /** @type {IDBDatabase} */ (e.target.result); var transaction = e.target.transaction; - + var fileStore; - + if (db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)) { fileStore = transaction.objectStore(IDBFS.DB_STORE_NAME); } else { fileStore = db.createObjectStore(IDBFS.DB_STORE_NAME); } - + if (!fileStore.indexNames.contains('timestamp')) { fileStore.createIndex('timestamp', 'timestamp', { unique: false }); } }; req.onsuccess = () => { db = /** @type {IDBDatabase} */ (req.result); - + // add to the cache IDBFS.dbs[name] = db; callback(null, db); @@ -1782,60 +1782,60 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, getLocalSet:(mount, callback) => { var entries = {}; - + function isRealDir(p) { return p !== '.' && p !== '..'; }; function toAbsolute(root) { return (p) => PATH.join2(root, p); }; - + var check = FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint)); - + while (check.length) { var path = check.pop(); var stat; - + try { stat = FS.lstat(path); } catch (e) { return callback(e); } - + if (FS.isDir(stat.mode)) { check.push(...FS.readdir(path).filter(isRealDir).map(toAbsolute(path))); } - + entries[path] = { 'timestamp': stat.mtime }; } - + return callback(null, { type: 'local', entries: entries }); }, getRemoteSet:(mount, callback) => { var entries = {}; - + IDBFS.getDB(mount.mountpoint, (err, db) => { if (err) return callback(err); - + try { var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readonly'); transaction.onerror = (e) => { callback(e.target.error); e.preventDefault(); }; - + var store = transaction.objectStore(IDBFS.DB_STORE_NAME); var index = store.index('timestamp'); - + index.openKeyCursor().onsuccess = (event) => { var cursor = event.target.result; - + if (!cursor) { return callback(null, { type: 'remote', db, entries }); } - + entries[cursor.primaryKey] = { 'timestamp': cursor.key }; - + cursor.continue(); }; } catch (e) { @@ -1845,7 +1845,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, loadLocalEntry:(path, callback) => { var stat, node; - + try { var lookup = FS.lookupPath(path); node = lookup.node; @@ -1853,7 +1853,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } catch (e) { return callback(e); } - + if (FS.isDir(stat.mode)) { return callback(null, { 'timestamp': stat.mtime, 'mode': stat.mode }); } else if (FS.isLink(stat.mode)) { @@ -1878,19 +1878,19 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } else { return callback(new Error('node type not supported')); } - + FS.chmod(path, entry['mode']); FS.utime(path, entry['timestamp'], entry['timestamp']); } catch (e) { return callback(e); } - + callback(null); }, removeLocalEntry:(path, callback) => { try { var stat = FS.lstat(path); - + if (FS.isDir(stat.mode)) { FS.rmdir(path); } else { @@ -1899,7 +1899,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } catch (e) { return callback(e); } - + callback(null); }, loadRemoteEntry:(store, path, callback) => { @@ -1933,7 +1933,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, reconcile:(src, dst, callback) => { var total = 0; - + var create = []; for (var [key, e] of Object.entries(src.entries)) { var e2 = dst.entries[key]; @@ -1942,7 +1942,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { total++; } } - + var remove = []; for (var key of Object.keys(dst.entries)) { if (!src.entries[key]) { @@ -1950,35 +1950,35 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { total++; } } - + if (!total) { return callback(null); } - + var errored = false; var db = src.type === 'remote' ? src.db : dst.db; var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readwrite'); var store = transaction.objectStore(IDBFS.DB_STORE_NAME); - + function done(err) { if (err && !errored) { errored = true; return callback(err); } }; - + // transaction may abort if (for example) there is a QuotaExceededError transaction.onerror = transaction.onabort = (e) => { done(e.target.error); e.preventDefault(); }; - + transaction.oncomplete = (e) => { if (!errored) { callback(null); } }; - + // sort paths in ascending order so directory entries are created // before the files inside them for (const path of create.sort()) { @@ -1994,7 +1994,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }); } } - + // sort paths in descending order so files are deleted before their // parent directories for (var path of remove.sort().reverse()) { @@ -2006,11 +2006,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }, }; - - - + + + var strError = (errno) => UTF8ToString(_strerror(errno)); - + var ERRNO_CODES = { 'EPERM': 63, 'ENOENT': 44, @@ -2134,16 +2134,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { 'EOWNERDEAD': 62, 'ESTRPIPE': 135, }; - + var asyncLoad = async (url) => { var arrayBuffer = await readAsync(url); assert(arrayBuffer, `Loading data file "${url}" failed (no arrayBuffer).`); return new Uint8Array(arrayBuffer); }; - - + + var FS_createDataFile = (...args) => FS.createDataFile(...args); - + var getUniqueRunDependency = (id) => { var orig = id; while (1) { @@ -2151,23 +2151,23 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { id = orig + Math.random(); } }; - + var dependenciesPromise = null; var resolveRunDependencies = async () => dependenciesPromise; var runDependencies = 0; - - + + var dependenciesPromiseResolve = null; - + var runDependencyTracking = { }; - + var runDependencyWatcher = null; var removeRunDependency = (id) => { runDependencies--; - + Module['monitorRunDependencies']?.(runDependencies); - + assert(id, 'removeRunDependency requires an ID'); assert(runDependencyTracking[id]); delete runDependencyTracking[id]; @@ -2179,18 +2179,18 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { dependenciesPromiseResolve(); } }; - - - - + + + + var addRunDependency = (id) => { if (!runDependencies) { dependenciesPromise = new Promise((resolve) => dependenciesPromiseResolve = resolve); } runDependencies++; - + Module['monitorRunDependencies']?.(runDependencies); - + assert(id, 'addRunDependency requires an ID') assert(!runDependencyTracking[id]); runDependencyTracking[id] = 1; @@ -2219,13 +2219,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { runDependencyWatcher.unref?.() } }; - - + + var preloadPlugins = []; var FS_handledByPreloadPlugin = async (byteArray, fullname) => { // Ensure plugins are ready. if (typeof Browser != 'undefined') Browser.init(); - + for (var plugin of preloadPlugins) { if (plugin['canHandle'](fullname)) { assert(plugin['handle'].constructor.name === 'AsyncFunction', 'Filesystem plugin handlers must be async functions (See #24914)') @@ -2242,13 +2242,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent; var dep = getUniqueRunDependency(`cp ${fullname}`); // might have several active requests for the same fullname addRunDependency(dep); - + try { var byteArray = url; if (typeof url == 'string') { byteArray = await asyncLoad(url); } - + byteArray = await FS_handledByPreloadPlugin(byteArray, fullname); preFinish?.(); if (!dontCreateFile) { @@ -2261,7 +2261,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var FS_createPreloadedFile = (parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) => { FS_preloadFile(parent, name, url, canRead, canWrite, dontCreateFile, canOwn, preFinish).then(onload).catch(onerror); }; - + var FS = { root:null, mounts:[], @@ -2408,31 +2408,31 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { throw new FS.ErrnoError(44); } opts.follow_mount ??= true - + if (!PATH.isAbs(path)) { path = FS.cwd() + '/' + path; } - + // limit max consecutive symlinks to SYMLOOP_MAX. linkloop: for (var nlinks = 0; nlinks < 40; nlinks++) { // split the absolute path var parts = path.split('/').filter((p) => !!p); - + // start at the root var current = FS.root; var current_path = '/'; - + for (var i = 0; i < parts.length; i++) { var islast = (i === parts.length-1); if (islast && opts.parent) { // stop resolving break; } - + if (parts[i] === '.') { continue; } - + if (parts[i] === '..') { current_path = PATH.dirname(current_path); if (FS.isRoot(current)) { @@ -2446,7 +2446,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } continue; } - + current_path = PATH.join2(current_path, parts[i]); try { current = FS.lookupNode(current, parts[i]); @@ -2459,12 +2459,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } throw e; } - + // jump to the mount's root node if this is a mountpoint if (FS.isMountpoint(current) && (!islast || opts.follow_mount)) { current = current.mounted.root; } - + // by default, lookupPath will not follow a symlink if it is the final path component. // setting opts.follow = true will override this behavior. if (FS.isLink(current.mode) && (!islast || opts.follow)) { @@ -2497,7 +2497,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, hashName(parentid, name) { var hash = 0; - + for (var i = 0; i < name.length; i++) { hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; } @@ -2541,9 +2541,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { createNode(parent, name, mode, rdev) { assert(typeof parent == 'object') var node = new FS.FSNode(parent, name, mode, rdev); - + FS.hashAddNode(node); - + return node; }, destroyNode(node) { @@ -2682,7 +2682,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { getStream:(fd) => FS.streams[fd], createStream(stream, fd = -1) { assert(fd >= -1); - + // clone it, so we can return an instance of FSStream stream = Object.assign(new FS.FSStream(), stream); if (fd == -1) { @@ -2736,15 +2736,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { getMounts(mount) { var mounts = []; var check = [mount]; - + while (check.length) { var m = check.pop(); - + mounts.push(m); - + check.push(...m.mounts); } - + return mounts; }, syncfs(populate, callback) { @@ -2752,22 +2752,22 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { callback = populate; populate = false; } - + FS.syncFSRequests++; - + if (FS.syncFSRequests > 1) { err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`); } - + var mounts = FS.getMounts(FS.root.mount); var completed = 0; - + function doCallback(errCode) { assert(FS.syncFSRequests > 0); FS.syncFSRequests--; return callback(errCode); } - + function done(errCode) { if (errCode) { if (!done.errored) { @@ -2780,7 +2780,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { doCallback(null); } }; - + // sync all mounts for (var mount of mounts) { if (mount.type.syncfs) { @@ -2799,77 +2799,77 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var root = mountpoint === '/'; var pseudo = !mountpoint; var node; - + if (root && FS.root) { throw new FS.ErrnoError(10); } else if (!root && !pseudo) { var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); - + mountpoint = lookup.path; // use the absolute path node = lookup.node; - + if (FS.isMountpoint(node)) { throw new FS.ErrnoError(10); } - + if (!FS.isDir(node.mode)) { throw new FS.ErrnoError(54); } } - + var mount = { type, opts, mountpoint, mounts: [] }; - + // create a root node for the fs var mountRoot = type.mount(mount); mountRoot.mount = mount; mount.root = mountRoot; - + if (root) { FS.root = mountRoot; } else if (node) { // set as a mountpoint node.mounted = mount; - + // add the new mount to the current mount's children if (node.mount) { node.mount.mounts.push(mount); } } - + return mountRoot; }, unmount(mountpoint) { var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); - + if (!FS.isMountpoint(lookup.node)) { throw new FS.ErrnoError(28); } - + // destroy the nodes for this mount, and all its child mounts var node = lookup.node; var mount = node.mounted; var mounts = FS.getMounts(mount); - + for (var [hash, current] of Object.entries(FS.nameTable)) { while (current) { var next = current.name_next; - + if (mounts.includes(current.mount)) { FS.destroyNode(current); } - + current = next; } } - + // no longer a mountpoint node.mounted = null; - + // remove this mount from the child mounts var idx = node.mount.mounts.indexOf(mount); assert(idx !== -1); @@ -2922,7 +2922,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { flags: 2, namelen: 255, }; - + if (node.node_ops.statfs) { Object.assign(rtn, node.node_ops.statfs(node.mount.opts.root)); } @@ -3005,13 +3005,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var new_name = PATH.basename(new_path); // parents must exist var lookup, old_dir, new_dir; - + // let the errors from non existent directories percolate up lookup = FS.lookupPath(old_path, { parent: true }); old_dir = lookup.node; lookup = FS.lookupPath(new_path, { parent: true }); new_dir = lookup.node; - + if (!old_dir || !new_dir) throw new FS.ErrnoError(44); // need to be part of the same mount if (old_dir.mount !== new_dir.mount) { @@ -3323,7 +3323,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } // we've already handled these, don't pass down to the underlying vfs flags &= ~(128 | 512 | 131072); - + // register the stream with the filesystem var stream = FS.createStream({ node, @@ -3595,7 +3595,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // TODO deprecate the old functionality of a single // input / output callback and that utilizes FS.createDevice // and instead require a unique set of stream ops - + // by default, we symlink the standard streams to the // default tty devices. however, if the standard streams // have been overwritten we create a unique device for @@ -3615,7 +3615,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } else { FS.symlink('/dev/tty1', '/dev/stderr'); } - + // open default streams for the stdin, stdout and stderr devices var stdin = FS.open('/dev/stdin', 0); var stdout = FS.open('/dev/stdout', 1); @@ -3626,13 +3626,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, staticInit() { FS.nameTable = new Array(4096); - + FS.mount(MEMFS, {}, '/'); - + FS.createDefaultDirectories(); FS.createDefaultDevices(); FS.createSpecialDirectories(); - + FS.filesystems = { 'MEMFS': MEMFS, 'IDBFS': IDBFS, @@ -3641,12 +3641,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { init(input, output, error) { assert(!FS.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); FS.initialized = true; - + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here input ??= Module['stdin']; output ??= Module['stdout']; error ??= Module['stderr']; - + FS.createStandardStreams(input, output, error); }, quit() { @@ -3827,27 +3827,27 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var header; var hasByteServing = (header = xhr.getResponseHeader('Accept-Ranges')) && header === 'bytes'; var usesGzip = (header = xhr.getResponseHeader('Content-Encoding')) && header === 'gzip'; - + var chunkSize = 1024*1024; // Chunk size in bytes - + if (!hasByteServing) chunkSize = datalength; - + // Function to get a range from the remote URL. var doXHR = (from, to) => { if (from > to) abort(`invalid range (${from}, ${to}) or no bytes requested!`); if (to > datalength-1) abort(`only ${datalength} bytes available! programmer error!`); - + // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); if (datalength !== chunkSize) xhr.setRequestHeader('Range', `bytes=${from}-${to}`); - + // Some hints to the browser that we want binary data. xhr.responseType = 'arraybuffer'; if (xhr.overrideMimeType) { xhr.overrideMimeType('text/plain; charset=x-user-defined'); } - + xhr.send(null); if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort(`Couldn't load ${url}. Status: ${xhr.status}`); if (xhr.response !== undefined) { @@ -3866,7 +3866,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (typeof lazyArray.chunks[chunkNum] == 'undefined') abort('doXHR failed!'); return lazyArray.chunks[chunkNum]; }); - + if (usesGzip || !datalength) { // if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file @@ -3874,7 +3874,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { chunkSize = datalength; out('LazyFiles on gzip forces download of the whole file when length is accessed'); } - + this._length = datalength; this._chunkSize = chunkSize; this.lengthKnown = true; @@ -3892,7 +3892,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return this._chunkSize; } } - + if (globalThis.XMLHttpRequest) { if (!ENVIRONMENT_IS_WORKER) abort('Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'); var lazyArray = new LazyUint8Array(); @@ -3900,7 +3900,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } else { var properties = { isDevice: false, url: url }; } - + var node = FS.createFile(parent, name, properties, canRead, canWrite); // This is a total hack, but I want to get this lazy file code out of the // core of MEMFS. If we want to keep this lazy file concept I feel it should @@ -3961,7 +3961,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return node; }, }; - + var SOCKFS = { websocketArgs:{ }, @@ -3986,14 +3986,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (flags) FS.getStream(fd)?.node.notifyListeners(flags); }, mount(mount) { - // The incoming Module['websocket'] can be used for configuring + // The incoming Module['websocket'] can be used for configuring // subprotocol/url, etc SOCKFS.websocketArgs = Module['websocket'] || {}; // Add the Event registration mechanism to the exported websocket configuration // object so we can register network callbacks from native JavaScript too. // For more documentation see system/include/emscripten/emscripten.h (Module['websocket'] ??= {})['on'] = SOCKFS.on; - + return FS.createNode(null, '/', 16895, 0); }, createSocket(family, type, protocol) { @@ -4013,7 +4013,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ) { throw new FS.ErrnoError(66); // if SOCK_STREAM, must be tcp or 0. } - + // create our internal socket structure var sock = { family, @@ -4026,12 +4026,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { recv_queue: [], sock_ops: SOCKFS.websocket_sock_ops }; - + // create the filesystem node to store the socket structure var name = SOCKFS.nextname(); var node = FS.createNode(SOCKFS.root, name, 49152, 0); node.sock = sock; - + // and the wrapping stream that enables library functions such // as read and write to indirectly interact with the socket var stream = FS.createStream({ @@ -4041,11 +4041,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { seekable: false, stream_ops: SOCKFS.stream_ops }); - + // map the new stream to the socket structure (sockets have a 1:1 // relationship with a stream) sock.stream = stream; - + return sock; }, getSocket(fd) { @@ -4110,13 +4110,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { websocket_sock_ops:{ createPeer(sock, addr, port) { var ws; - + if (typeof addr == 'object') { ws = addr; addr = null; port = null; } - + if (ws) { // for sockets that've already connected (e.g. we're the server) // we can inspect the _socket property for the address @@ -4144,7 +4144,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var subProtocols = 'binary'; // The default value is 'binary' // The default WebSocket options var opts = undefined; - + // Fetch runtime WebSocket URL config. if (SOCKFS.websocketArgs['url']) { url = SOCKFS.websocketArgs['url']; @@ -4155,20 +4155,20 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } else if (SOCKFS.websocketArgs['subprotocol'] === null) { subProtocols = 'null' } - + if (url === 'ws://' || url === 'wss://') { // Is the supplied URL config just a prefix, if so complete it. var parts = addr.split('/'); url = url + parts[0] + ':' + port + '/' + parts.slice(1).join('/'); } - + if (subProtocols !== 'null') { // The regex trims the string (removes spaces at the beginning and end), then splits the string by // , into an Array. Whitespace removal is important for Websockify and ws. subProtocols = subProtocols.replace(/^ +| +$/g,'').split(/ *, */); - + opts = subProtocols; } - + // If node we use the ws library. var WebSocketConstructor; if (ENVIRONMENT_IS_NODE) { @@ -4183,17 +4183,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { throw new FS.ErrnoError(23); } } - + var peer = { addr, port, socket: ws, msg_send_queue: [] }; - + SOCKFS.websocket_sock_ops.addPeer(sock, peer); SOCKFS.websocket_sock_ops.handlePeerEvents(sock, peer); - + // if this is a bound dgram socket, send the port number first to allow // us to override the ephemeral port reported to us by remotePort on the // remote end. @@ -4204,7 +4204,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ((sock.sport & 0xff00) >> 8) , (sock.sport & 0xff) ])); } - + return peer; }, getPeer(sock, addr, port) { @@ -4218,12 +4218,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, handlePeerEvents(sock, peer) { var first = true; - + function handleOpen() { - + sock.connecting = false; SOCKFS.emit('open', sock.stream.fd); - + try { var queued = peer.msg_send_queue.shift(); while (queued) { @@ -4236,7 +4236,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { peer.socket.close(); } } - + function handleMessage(data) { if (typeof data == 'string') { var encoder = new TextEncoder(); // should be utf-8 @@ -4251,7 +4251,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } data = new Uint8Array(data); // make a typed array view on the array buffer } - + // if this is the port message, override the peer's port with it var wasfirst = first; first = false; @@ -4266,11 +4266,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { SOCKFS.websocket_sock_ops.addPeer(sock, peer); return; } - + sock.recv_queue.push({ addr: peer.addr, port: peer.port, data: data }); SOCKFS.emit('message', sock.stream.fd); } - + if (ENVIRONMENT_IS_NODE) { // EventEmitter-style events use by ws library objects in Node.js). peer.socket.on('open', handleOpen); @@ -4307,24 +4307,24 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // if there are pending clients. return sock.pending.length ? (64 | 1) : 0; } - + var mask = 0; var dest = sock.type === 1 ? // we only care about the socket state for connection-based sockets SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport) : null; - + if (sock.recv_queue.length || !dest || // connection-less sockets are always ready to read (dest && dest.socket.readyState === dest.socket.CLOSING) || (dest && dest.socket.readyState === dest.socket.CLOSED)) { // let recv return 0 once closed mask |= (64 | 1); } - + if (!dest || // connection-less sockets are always ready to write (dest && dest.socket.readyState === dest.socket.OPEN)) { mask |= 4; } - + if ((dest && dest.socket.readyState === dest.socket.CLOSING) || (dest && dest.socket.readyState === dest.socket.CLOSED)) { // When an non-blocking connect fails mark the socket as writable. @@ -4338,7 +4338,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { mask |= 16 | 8192; } } - + return mask; }, ioctl(sock, request, arg) { @@ -4410,11 +4410,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (sock.server) { throw new FS.ErrnoError(138); } - + // TODO autobind // if (!sock.addr && sock.type == 2) { // } - + // early out if we're already connected / in the middle of connecting if (typeof sock.daddr != 'undefined' && typeof sock.dport != 'undefined') { var dest = SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport); @@ -4426,13 +4426,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } } } - + // add the socket to our peer list and set our // destination address / port to match var peer = SOCKFS.websocket_sock_ops.createPeer(sock, addr, port); sock.daddr = peer.addr; sock.dport = peer.port; - + // because we cannot synchronously block to wait for the WebSocket // connection to complete, we return here pretending that the connection // was a success. @@ -4453,16 +4453,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // TODO support backlog }); SOCKFS.emit('listen', sock.stream.fd); // Send Event with listen fd. - + sock.server.on('connection', (ws) => { if (sock.type === 1) { var newsock = SOCKFS.createSocket(sock.family, sock.type, sock.protocol); - + // create a peer on the new socket var peer = SOCKFS.websocket_sock_ops.createPeer(newsock, ws); newsock.daddr = peer.addr; newsock.dport = peer.port; - + // push to queue for accept to pick up sock.pending.push(newsock); SOCKFS.emit('connection', newsock.stream.fd); @@ -4533,17 +4533,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { addr = sock.daddr; port = sock.dport; } - + // find the peer for the destination address var dest = SOCKFS.websocket_sock_ops.getPeer(sock, addr, port); - + // early out if not connected with a connection-based socket if (sock.type === 1) { if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) { throw new FS.ErrnoError(53); } } - + // create a copy of the incoming data to send, as the WebSocket API // doesn't work entirely with an ArrayBufferView, it'll just send // the entire underlying buffer @@ -4551,9 +4551,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { offset += buffer.byteOffset; buffer = buffer.buffer; } - + var data = buffer.slice(offset, offset + length); - + // if we don't have a cached connectionless UDP datagram connection, or // the TCP socket is still connecting, queue the message to be sent upon // connect, and lie, saying the data was sent now. @@ -4567,7 +4567,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { dest.msg_send_queue.push(data); return length; } - + try { // send the actual data dest.socket.send(data); @@ -4582,7 +4582,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // tcp servers should not be recv()'ing on the listen socket throw new FS.ErrnoError(53); } - + // MSG_PEEK returns the head of the queue without consuming it, so a // later recv sees the same bytes and poll still reports it readable. var peek = flags & 2; @@ -4590,7 +4590,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (!queued) { if (sock.type === 1) { var dest = SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport); - + if (!dest) { // if we have a destination address but are not connected, error out throw new FS.ErrnoError(53); @@ -4604,7 +4604,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } throw new FS.ErrnoError(6); } - + // queued.data will be an ArrayBuffer if it's unadulterated, but if it's // requeued TCP data it'll be an ArrayBufferView var queuedLength = queued.data.byteLength || queued.data.length; @@ -4616,28 +4616,28 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { addr: queued.addr, port: queued.port }; - + if (peek) return res; sock.recv_queue.shift(); - + // push back any unread data for TCP connections if (sock.type === 1 && bytesRead < queuedLength) { var bytesRemaining = queuedLength - bytesRead; queued.data = new Uint8Array(queuedBuffer, queuedOffset + bytesRead, bytesRemaining); sock.recv_queue.unshift(queued); } - + return res; }, }, }; - + var getSocketFromFD = (fd) => { var socket = SOCKFS.getSocket(fd); if (!socket) throw new FS.ErrnoError(8); return socket; }; - + var inetPton4 = (str) => { var b = str.split('.'); for (var i = 0; i < 4; i++) { @@ -4647,7 +4647,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) >>> 0; }; - + var inetPton6 = (str) => { var words; var w, offset, z, i; @@ -4666,7 +4666,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } else { str = str.replace('::', ':Z:'); } - + if (str.indexOf('.') > 0) { // parse IPv4 embedded address str = str.replace(new RegExp('[.]', 'g'), ':'); @@ -4677,7 +4677,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } else { words = str.split(':'); } - + offset = 0; z = 0; for (w=0; w < words.length; w++) { if (typeof words[w] == 'string') { @@ -4703,9 +4703,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { (parts[7] << 16) | parts[6] ]; }; - - - + + + var DNS = { address_map:{ id:1, @@ -4724,37 +4724,37 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (res !== null) { return name; } - + // See if this name is already mapped. var addr; - + if (DNS.address_map.addrs[name]) { addr = DNS.address_map.addrs[name]; } else { var id = DNS.address_map.id++; assert(id < 65535, 'exceeded max address mappings of 65535'); - + addr = '172.29.' + (id & 0xff) + '.' + (id & 0xff00); - + DNS.address_map.names[addr] = name; DNS.address_map.addrs[name] = addr; } - + return addr; }, lookup_addr(addr) { if (DNS.address_map.names[addr]) { return DNS.address_map.names[addr]; } - + return null; }, }; - - + + /** @type {!Int16Array} */ var HEAP16; - + /** @param {number=} addrlen */ var writeSockaddr = (sa, family, addr, port, addrlen) => { switch (family) { @@ -4791,7 +4791,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; function ___syscall_accept4(fd, addr, len, flags, u1, u2) { try { - + var sock = getSocketFromFD(fd); var newsock = sock.sock_ops.accept(sock); if (addr) { @@ -4804,13 +4804,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - + + var inetNtop4 = (addr) => (addr & 0xff) + '.' + ((addr >> 8) & 0xff) + '.' + ((addr >> 16) & 0xff) + '.' + ((addr >> 24) & 0xff); - - + + var inetNtop6 = (ints) => { // ref: http://www.ietf.org/rfc/rfc2373.txt - section 2.5.4 // Format for IPv4 compatible and mapped 128-bit IPv6 Addresses @@ -4843,9 +4843,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ints[3] & 0xffff, (ints[3] >> 16) ]; - + // Handle IPv4-compatible, IPv4-mapped, loopback and any/unspecified addresses - + var hasipv4 = true; var v4part = ''; // check if the 10 high-order bytes are all zeros (first 5 words) @@ -4855,7 +4855,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { break; } } - + if (hasipv4) { // low-order 32-bits store an IPv4 address (bytes 13 to 16) (last 2 words) v4part = inetNtop4(parts[6] | (parts[7] << 16)); @@ -4875,9 +4875,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return str; } } - + // Handle all other IPv6 addresses - + // first run to find the longest contiguous zero words for (word = 0; word < 8; word++) { if (!parts[word]) { @@ -4892,7 +4892,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { zstart = word - longest + 1; } } - + for (word = 0; word < 8; word++) { if (longest > 1) { // compress contiguous zeros - to produce '::' @@ -4910,18 +4910,18 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return str; }; - - - + + + /** @type {!Uint16Array} */ var HEAPU16; - + var readSockaddr = (sa, salen) => { // family / port offsets are common to both sockaddr_in and sockaddr_in6 var family = HEAP16[((sa)>>1)]; var port = _ntohs(HEAPU16[(((sa)+(2))>>1)]); var addr; - + switch (family) { case 2: if (salen !== 16) { @@ -4945,11 +4945,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { default: return { errno: 5 }; } - + return { family: family, addr: addr, port: port }; }; - - + + var getSocketAddress = (addrp, addrlen) => { var info = readSockaddr(addrp, addrlen); if (info.errno) throw new FS.ErrnoError(info.errno); @@ -4958,7 +4958,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; function ___syscall_bind(fd, addr, len, u1, u2, u3) { try { - + var sock = getSocketFromFD(fd); var info = getSocketAddress(addr, len); sock.sock_ops.bind(sock, info.addr, info.port); @@ -4968,12 +4968,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - + + function ___syscall_connect(fd, addr, len, u1, u2, u3) { try { - + var sock = getSocketFromFD(fd); var info = getSocketAddress(addr, len); sock.sock_ops.connect(sock, info.addr, info.port); @@ -4983,14 +4983,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - - - - - - - + + + + + + + + /** not-@type {!BigInt64Array} */ var HEAP64; var SYSCALLS = { @@ -5072,7 +5072,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; function ___syscall_faccessat(dirfd, path, amode, flags) { try { - + path = SYSCALLS.getStr(path); assert(!flags || flags == 512); path = SYSCALLS.calculateAt(dirfd, path); @@ -5098,7 +5098,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + var syscallGetVarargI = () => { assert(SYSCALLS.varargs != undefined); @@ -5108,13 +5108,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return ret; }; var syscallGetVarargP = syscallGetVarargI; - - - + + + function ___syscall_fcntl64(fd, cmd, varargs) { SYSCALLS.varargs = varargs; try { - + var stream = SYSCALLS.getStreamFromFD(fd); switch (cmd) { case 0: { @@ -5161,29 +5161,29 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_fstat64(fd, buf) { try { - + return SYSCALLS.writeStat(buf, FS.fstat(fd)); } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; return -e.errno; } } - + var INT53_MAX = 9007199254740992; - + var INT53_MIN = -9007199254740992; var bigintToI53Checked = (num) => (num < INT53_MIN || num > INT53_MAX) ? NaN : Number(num); function ___syscall_ftruncate64(fd, length) { length = bigintToI53Checked(length); - - + + try { - + if (isNaN(length)) return -22; FS.ftruncate(fd, length); return 0; @@ -5194,15 +5194,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ; } - - + + var stringToUTF8 = (str, outPtr, maxBytesToWrite) => { assert(typeof maxBytesToWrite == 'number', 'stringToUTF8 requires a third parameter that specifies the length of the output buffer'); return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); }; function ___syscall_getcwd(buf, size) { try { - + if (!size) return -28; var cwd = FS.cwd(); var cwdLengthInBytes = lengthBytesUTF8(cwd) + 1; @@ -5214,22 +5214,22 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - - - - + + + + + function ___syscall_getdents64(fd, dirp, count) { try { - + var stream = SYSCALLS.getStreamFromFD(fd) stream.getdents ||= FS.readdir(stream.path); - + var struct_size = 280; var pos = 0; var off = FS.llseek(stream, 0, 1); - + var startIdx = Math.floor(off / struct_size); var endIdx = Math.min(stream.getdents.length, startIdx + Math.floor(count/struct_size)) for (var idx = startIdx; idx < endIdx; idx++) { @@ -5278,12 +5278,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - + + function ___syscall_getpeername(fd, addr, len, u1, u2, u3) { try { - + var sock = getSocketFromFD(fd); if (!sock.daddr) { return -53; // The socket is not connected. @@ -5296,12 +5296,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - + + function ___syscall_getsockname(fd, addr, len, u1, u2, u3) { try { - + var sock = getSocketFromFD(fd); var defaultAddr = '0.0.0.0'; // TODO: sock.saddr should never be undefined, see TODO in websocket_sock_ops.getname @@ -5313,12 +5313,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - + + function ___syscall_getsockopt(fd, level, optname, optval, optlen, unused) { try { - + var sock = getSocketFromFD(fd); // Minimal getsockopt aimed at resolving https://github.com/emscripten-core/emscripten/issues/2211 // so only supports SOL_SOCKET with SO_ERROR. @@ -5336,16 +5336,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - - - - + + + + + function ___syscall_ioctl(fd, op, varargs) { SYSCALLS.varargs = varargs; try { - + var stream = SYSCALLS.getStreamFromFD(fd); switch (op) { case 21509: { @@ -5437,11 +5437,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_listen(fd, backlog, u1, u2, u3, u4) { try { - + var sock = getSocketFromFD(fd); sock.sock_ops.listen(sock, backlog); return 0; @@ -5450,11 +5450,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_lstat64(path, buf) { try { - + path = SYSCALLS.getStr(path); return SYSCALLS.writeStat(buf, FS.lstat(path)); } catch (e) { @@ -5462,11 +5462,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_mkdirat(dirfd, path, mode) { try { - + path = SYSCALLS.getStr(path); path = SYSCALLS.calculateAt(dirfd, path); mode &= ~SYSCALLS.currentUmask; @@ -5477,11 +5477,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_newfstatat(dirfd, path, buf, flags) { try { - + path = SYSCALLS.getStr(path); var nofollow = flags & 256; var allowEmpty = flags & 4096; @@ -5494,13 +5494,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - + + function ___syscall_openat(dirfd, path, flags, varargs) { SYSCALLS.varargs = varargs; try { - + path = SYSCALLS.getStr(path); path = SYSCALLS.calculateAt(dirfd, path); var mode = varargs ? syscallGetVarargI() : 0; @@ -5513,7 +5513,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + var pollOne = (fd, events) => { var stream = FS.getStream(fd); @@ -5525,8 +5525,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { : 5; return flags & (events | 8 | 16 | 32); }; - - + + var doPollSync = (fds, nfds) => { var count = 0; for (var i = 0, pollfd = fds; i < nfds; i++, pollfd += 8) { @@ -5540,7 +5540,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; function ___syscall_poll(fds, nfds, timeout) { try { - + var count = doPollSync(fds, nfds); if (!count && timeout != 0) warnOnce('non-zero poll() timeout not supported: ' + timeout) return count; @@ -5549,13 +5549,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - - + + + function ___syscall_recvfrom(fd, buf, len, flags, addr, alen) { try { - + var sock = getSocketFromFD(fd); var msg = sock.sock_ops.recvmsg(sock, len, flags); if (!msg) return 0; // socket is closed @@ -5570,11 +5570,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_renameat(olddirfd, oldpath, newdirfd, newpath) { try { - + oldpath = SYSCALLS.getStr(oldpath); newpath = SYSCALLS.getStr(newpath); oldpath = SYSCALLS.calculateAt(olddirfd, oldpath); @@ -5586,11 +5586,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_rmdir(path) { try { - + path = SYSCALLS.getStr(path); FS.rmdir(path); return 0; @@ -5599,13 +5599,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - - + + + function ___syscall_sendto(fd, buf, len, flags, addr, alen) { try { - + var sock = getSocketFromFD(fd); if (!addr) { // send, no address provided @@ -5619,11 +5619,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_setsockopt(fd, level, optname, optval, optlen, unused) { try { - + getSocketFromFD(fd); // validate the fd (and keep this syscall's catch reachable) return -50; // The option is unknown at the level indicated. } catch (e) { @@ -5631,11 +5631,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_shutdown(fd, how, u1, u2, u3, u4) { try { - + var sock = getSocketFromFD(fd); return -52; // unsupported feature } catch (e) { @@ -5643,11 +5643,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_socket(domain, type, protocol, u1, u2, u3) { try { - + var sock = SOCKFS.createSocket(domain, type, protocol); return sock.stream.fd; } catch (e) { @@ -5655,11 +5655,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_stat64(path, buf) { try { - + path = SYSCALLS.getStr(path); return SYSCALLS.writeStat(buf, FS.stat(path)); } catch (e) { @@ -5667,11 +5667,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + function ___syscall_unlinkat(dirfd, path, flags) { try { - + path = SYSCALLS.getStr(path); path = SYSCALLS.calculateAt(dirfd, path); if (!flags) { @@ -5687,17 +5687,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - - + + var readI53FromI64 = (ptr) => { return HEAPU32[((ptr)>>2)] + HEAP32[(((ptr)+(4))>>2)] * 4294967296; }; - - + + function ___syscall_utimensat(dirfd, path, times, flags) { try { - + var nofollow = flags & 256; path = SYSCALLS.getStr(path); path = SYSCALLS.calculateAt(dirfd, path, true); @@ -5737,27 +5737,27 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -e.errno; } } - + var getCppExceptionTag = () => ___cpp_exception; - - + + var getCppExceptionThrownObjectFromWebAssemblyException = (ex) => { // In Wasm EH, the value extracted from WebAssembly.Exception is a pointer // to the unwind header. Convert it to the actual thrown value. var unwind_header = ex.getArg(getCppExceptionTag(), 0); return ___thrown_object_from_unwind_exception(unwind_header); }; - - - + + + var stackSave = () => _emscripten_stack_get_current(); - + var stackRestore = (val) => __emscripten_stack_restore(val); - + var stackAlloc = (sz) => __emscripten_stack_alloc(sz); - - + + var getExceptionMessageCommon = (ptr) => { var sp = stackSave(); var type_addr_addr = stackAlloc(4); @@ -5779,14 +5779,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex); return getExceptionMessageCommon(ptr); }; - - + + var decrementExceptionRefcount = (ex) => { var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex); ___cxa_decrement_exception_refcount(ptr); }; - - + + var incrementExceptionRefcount = (ex) => { var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex); ___cxa_increment_exception_refcount(ptr); @@ -5800,9 +5800,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var __abort_js = () => abort('native code called abort()'); - - - + + + var __emscripten_lookup_name = (name) => { // uint32_t _emscripten_lookup_name(const char *name); var nameString = UTF8ToString(name); @@ -5810,22 +5810,22 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; var isLeapYear = (year) => year%4 === 0 && (year%100 !== 0 || year%400 === 0); - + var MONTH_DAYS_LEAP_CUMULATIVE = [0,31,60,91,121,152,182,213,244,274,305,335]; - + var MONTH_DAYS_REGULAR_CUMULATIVE = [0,31,59,90,120,151,181,212,243,273,304,334]; var ydayFromDate = (date) => { var leap = isLeapYear(date.getFullYear()); var monthDaysCumulative = (leap ? MONTH_DAYS_LEAP_CUMULATIVE : MONTH_DAYS_REGULAR_CUMULATIVE); var yday = monthDaysCumulative[date.getMonth()] + date.getDate() - 1; // -1 since it's days since Jan 1 - + return yday; }; - - + + var __mktime_js = function(tmPtr) { - - var ret = (() => { + + var ret = (() => { var date = new Date(HEAP32[(((tmPtr)+(20))>>2)] + 1900, HEAP32[(((tmPtr)+(16))>>2)], HEAP32[(((tmPtr)+(12))>>2)], @@ -5836,7 +5836,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (isNaN(date.getTime())) { return -1; } - + // There's an ambiguous hour when the time goes back; the tm_isdst field is // used to disambiguate it. Date() basically guesses, so we fix it up if it // guessed wrong, or fill in tm_isdst with the guess if it's -1. @@ -5858,7 +5858,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -1; } } - + HEAP32[(((tmPtr)+(32))>>2)] = dst; HEAP32[(((tmPtr)+(24))>>2)] = date.getDay(); var yday = ydayFromDate(date)|0; @@ -5870,26 +5870,26 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP32[(((tmPtr)+(12))>>2)] = date.getDate(); HEAP32[(((tmPtr)+(16))>>2)] = date.getMonth(); HEAP32[(((tmPtr)+(20))>>2)] = date.getYear(); - + // Return time in seconds return date.getTime() / 1000; })(); return BigInt(ret); }; - - - - - - - + + + + + + + function __mmap_js(len, prot, flags, fd, offset, allocated, addr) { offset = bigintToI53Checked(offset); - - + + try { - + // musl's mmap doesn't allow values over a certain limit // see OFF_MASK in mmap.c. assert(!isNaN(offset)); @@ -5906,13 +5906,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ; } - + function __munmap_js(addr, len, prot, flags, fd, offset) { offset = bigintToI53Checked(offset); - - + + try { - + var stream = SYSCALLS.getStreamFromFD(fd); if (prot & 2) { SYSCALLS.doMsync(addr, stream, len, flags, offset); @@ -5924,9 +5924,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ; } - - - + + + var __tzset_js = (timezone, daylight, std_name, dst_name) => { // TODO: Use (malleable) environment variables instead of system settings. var currentYear = new Date().getFullYear(); @@ -5934,7 +5934,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var summer = new Date(currentYear, 6, 1); var winterOffset = winter.getTimezoneOffset(); var summerOffset = summer.getTimezoneOffset(); - + // Local standard timezone offset. Local standard time is not adjusted for // daylight savings. This code uses the fact that getTimezoneOffset returns // a greater value during Standard Time versus Daylight Saving Time (DST). @@ -5942,28 +5942,28 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // compares whether the output of the given date the same (Standard) or less // (DST). var stdTimezoneOffset = Math.max(winterOffset, summerOffset); - + // timezone is specified as seconds west of UTC ("The external variable // `timezone` shall be set to the difference, in seconds, between // Coordinated Universal Time (UTC) and local standard time."), the same // as returned by stdTimezoneOffset. // See http://pubs.opengroup.org/onlinepubs/009695399/functions/tzset.html HEAPU32[((timezone)>>2)] = stdTimezoneOffset * 60; - + HEAP32[((daylight)>>2)] = Number(winterOffset != summerOffset); - + var extractZone = (timezoneOffset) => { // Why inverse sign? // Read here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset var sign = timezoneOffset >= 0 ? '-' : '+'; - + var absOffset = Math.abs(timezoneOffset) var hours = String(Math.floor(absOffset / 60)).padStart(2, '0'); var minutes = String(absOffset % 60).padStart(2, '0'); - + return `UTC${sign}${hours}${minutes}`; } - + var winterName = extractZone(winterOffset); var summerName = extractZone(summerOffset); assert(winterName); @@ -5981,18 +5981,18 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; var _emscripten_get_now = () => performance.now(); - + var _emscripten_date_now = () => Date.now(); - + var nowIsMonotonic = 1; - + var checkWasiClock = (clock_id) => clock_id >= 0 && clock_id <= 3; - - + + function _clock_time_get(clk_id, ignored_precision, ptime) { ignored_precision = bigintToI53Checked(ignored_precision); - - + + if (!checkWasiClock(clk_id)) { return 28; } @@ -6029,8 +6029,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } quit_(1, e); }; - - + + var runtimeKeepaliveCounter = 0; var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; var _proc_exit = (code) => { @@ -6041,25 +6041,25 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } quit_(code, new ExitStatus(code)); }; - - + + /** @param {boolean|number=} implicit */ var exitJS = (status, implicit) => { EXITSTATUS = status; - + checkUnflushedContent(); - + // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down if (keepRuntimeAlive() && !implicit) { var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`; err(msg); } - + _proc_exit(status); }; var _exit = exitJS; - - + + var maybeExit = () => { if (!keepRuntimeAlive()) { try { @@ -6082,26 +6082,26 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { maybeExit(); } }; - + function getFullscreenElement() { return document.fullscreenElement ?? document.webkitFullscreenElement ; } - + /** @param {number=} timeout */ var safeSetTimeout = (func, timeout) => { - + return setTimeout(() => { - + callUserCallback(func); }, timeout); }; - - - - - + + + + + var Browser = { useWebGL:false, isFullscreen:false, @@ -6115,7 +6115,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { init() { if (Browser.initted) return; Browser.initted = true; - + // Support for plugins that can process preloaded files. You can add more of these to // your app by creating and appending to preloadPlugins. // @@ -6123,7 +6123,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // it is given the file's raw data. When it is done, it calls a callback with the file's // (possibly modified) data. For example, a plugin might decompress a file, or it // might create some side data structure for use later (like an Image element, etc.). - + var imagePlugin = {}; imagePlugin['canHandle'] = (name) => { return !Module['noImageDecoding'] && /\.(jpg|jpeg|png|bmp|webp)$/i.test(name); @@ -6156,7 +6156,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }); }; preloadPlugins.push(imagePlugin); - + var audioPlugin = {}; audioPlugin['canHandle'] = (name) => { return !Module['noAudioDecoding'] && name.slice(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 }; @@ -6212,9 +6212,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }); }; preloadPlugins.push(audioPlugin); - + // Canvas event setup - + function pointerLockChange() { var canvas = Browser.getCanvas(); Browser.pointerLock = document.pointerLockElement === canvas; @@ -6223,9 +6223,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (canvas) { // forced aspect ratio can be enabled by defining 'forcedAspectRatio' on Module // Module['forcedAspectRatio'] = 4 / 3; - + document.addEventListener('pointerlockchange', pointerLockChange); - + if (Module['elementPointerLock']) { canvas.addEventListener('click', (ev) => { if (!Browser.pointerLock && Browser.getCanvas().requestPointerLock) { @@ -6238,7 +6238,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, createContext(/** @type {HTMLCanvasElement} */ canvas, useWebGL, setInModule, webGLContextAttributes) { if (useWebGL && Module['ctx'] && canvas == Browser.getCanvas()) return Module['ctx']; // no need to recreate GL context if it's already been created for this canvas. - + var ctx; var contextHandle; if (useWebGL) { @@ -6248,13 +6248,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { alpha: false, majorVersion: 1, }; - + if (webGLContextAttributes) { for (var attribute in webGLContextAttributes) { contextAttributes[attribute] = webGLContextAttributes[attribute]; } } - + // This check of existence of GL is here to satisfy Closure compiler, which yells if variable GL is referenced below but GL object is not // actually compiled in because application is not doing any GL operations. TODO: Ideally if GL is not being used, this function // Browser.createContext() should not even be emitted. @@ -6267,9 +6267,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } else { ctx = canvas.getContext('2d'); } - + if (!ctx) return null; - + if (setInModule) { if (!useWebGL) assert(typeof GLctx == 'undefined', 'cannot set in module if GLctx is used, but we are a non-GL context that would replace it'); Module['ctx'] = ctx; @@ -6288,7 +6288,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { Browser.resizeCanvas = resizeCanvas; if (typeof Browser.lockPointer == 'undefined') Browser.lockPointer = true; if (typeof Browser.resizeCanvas == 'undefined') Browser.resizeCanvas = false; - + var canvas = Browser.getCanvas(); function fullscreenChange() { Browser.isFullscreen = false; @@ -6306,7 +6306,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // remove the full screen specific parent of the canvas again to restore the HTML structure from before going full screen canvasContainer.parentNode.insertBefore(canvas, canvasContainer); canvasContainer.parentNode.removeChild(canvasContainer); - + if (Browser.resizeCanvas) { Browser.setWindowedCanvasSize(); } else { @@ -6314,25 +6314,25 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } } } - + if (!Browser.fullscreenHandlersInstalled) { Browser.fullscreenHandlersInstalled = true; document.addEventListener('fullscreenchange', fullscreenChange); document.addEventListener('webkitfullscreenchange', fullscreenChange); } - + // create a new parent to ensure the canvas has no siblings. this allows browsers to optimize full screen performance when its parent is the full screen root var canvasContainer = document.createElement('div'); canvas.parentNode.insertBefore(canvasContainer, canvas); canvasContainer.appendChild(canvas); - + // use parent of canvas as full screen root to allow aspect ratio correction (Firefox stretches the root to screen size) // Safari didn't support Element.requestFullscreen until 16.4 // See: https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen /** @suppress {checkTypes} */ canvasContainer.requestFullscreen ??= (canvasContainer['webkitRequestFullscreen'] ? () => canvasContainer['webkitRequestFullscreen'](Element.ALLOW_KEYBOARD_INPUT) : null) ?? (canvasContainer['webkitRequestFullScreen'] ? () => canvasContainer['webkitRequestFullScreen'](Element.ALLOW_KEYBOARD_INPUT) : null); - + canvasContainer.requestFullscreen(); }, exitFullscreen() { @@ -6342,7 +6342,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (!Browser.isFullscreen) { return false; } - + var CFS = document.exitFullscreen ?? document['webkitCancelFullScreen']; CFS.apply(document, []); return true; @@ -6415,16 +6415,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // in the coordinates. var canvas = Browser.getCanvas(); var rect = canvas.getBoundingClientRect(); - + var adjustedX = pageX - (window.scrollX + rect.left); var adjustedY = pageY - (window.scrollY + rect.top); - + // the canvas might be CSS-scaled compared to its backbuffer; // SDL-using content will want mouse coordinates in terms // of backbuffer units. adjustedX = adjustedX * (canvas.width / rect.width); adjustedY = adjustedY * (canvas.height / rect.height); - + return { x: adjustedX, y: adjustedY }; }, setMouseCoords(pageX, pageY) { @@ -6440,7 +6440,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // based on the movement of the mouse. Browser.mouseMovementX = event.movementX; Browser.mouseMovementY = event.movementY; - + // add the mouse delta to the current absolute mouse position Browser.mouseX += Browser.mouseMovementX; Browser.mouseY += Browser.mouseMovementY; @@ -6449,10 +6449,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var touch = event.touch; if (touch === undefined) { return; // the 'touch' property is only defined in SDL - + } var coords = Browser.calculateMouseCoords(touch.pageX, touch.pageY); - + if (event.type === 'touchstart') { Browser.lastTouches[touch.identifier] = coords; Browser.touches[touch.identifier] = coords; @@ -6464,7 +6464,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return; } - + Browser.setMouseCoords(event.pageX, event.pageY); } }, @@ -6537,9 +6537,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }, }; - - - + + + var EGL = { errorCode:12288, defaultDisplayInitialized:false, @@ -6562,7 +6562,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } - + if (attribList) { // read attribList if it is non-null for (;;) { @@ -6591,7 +6591,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { attribList += 8; } } - + if ((!config || !config_size) && !numConfigs) { EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); return 0; @@ -6602,7 +6602,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (config && config_size > 0) { HEAPU32[((config)>>2)] = 62002; } - + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); return 1; }, @@ -6621,7 +6621,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { EGL.chooseConfig(display, attrib_list, configs, config_size, numConfigs); var GLctx; - + var webgl_enable_ANGLE_instanced_arrays = (ctx) => { // Extension available in WebGL 1 from Firefox 26 and Google Chrome 30 onwards. Core feature in WebGL 2. var ext = ctx.getExtension('ANGLE_instanced_arrays'); @@ -6635,7 +6635,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 1; } }; - + var webgl_enable_OES_vertex_array_object = (ctx) => { // Extension available in WebGL 1 from Firefox 25 and WebKit 536.28/desktop Safari 6.0.3 onwards. Core feature in WebGL 2. var ext = ctx.getExtension('OES_vertex_array_object'); @@ -6647,7 +6647,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 1; } }; - + var webgl_enable_WEBGL_draw_buffers = (ctx) => { // Extension available in WebGL 1 from Firefox 28 onwards. Core feature in WebGL 2. var ext = ctx.getExtension('WEBGL_draw_buffers'); @@ -6656,20 +6656,20 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 1; } }; - + var webgl_enable_EXT_polygon_offset_clamp = (ctx) => !!(ctx.extPolygonOffsetClamp = ctx.getExtension('EXT_polygon_offset_clamp')); - + var webgl_enable_EXT_clip_control = (ctx) => !!(ctx.extClipControl = ctx.getExtension('EXT_clip_control')); - + var webgl_enable_WEBGL_polygon_mode = (ctx) => !!(ctx.webglPolygonMode = ctx.getExtension('WEBGL_polygon_mode')); - + var webgl_enable_WEBGL_multi_draw = (ctx) => // Closure is expected to be allowed to minify the '.multiDrawWebgl' property, so not accessing it quoted. !!(ctx.multiDrawWebgl = ctx.getExtension('WEBGL_multi_draw')); - + var getEmscriptenSupportedExtensions = (ctx) => { // Restrict the list of advertised extensions to those that we actually // support. @@ -6717,10 +6717,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // .getSupportedExtensions() can return null if context is lost, so coerce to empty array. return ctx.getSupportedExtensions()?.filter(ext => supportedExtensions.includes(ext)) ?? []; }; - - - - + + + + var GL = { counter:1, buffers:[], @@ -6773,7 +6773,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return source; }, createContext:(/** @type {HTMLCanvasElement} */ canvas, webGLContextAttributes) => { - + // BUG: Workaround Safari WebGL issue: After successfully acquiring WebGL // context on a canvas, calling .getContext() will always return that // context independent of which 'webgl' or 'webgl2' @@ -6792,27 +6792,27 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } canvas.getContext = fixedGetContext; } - + var ctx = canvas.getContext('webgl', webGLContextAttributes); - + if (!ctx) return 0; - + var handle = GL.registerContext(ctx, webGLContextAttributes); - + return handle; }, registerContext:(ctx, webGLContextAttributes) => { // without pthreads a context is just an integer ID var handle = GL.getNewId(GL.contexts); - + var context = { handle, attributes: webGLContextAttributes, version: webGLContextAttributes.majorVersion, GLctx: ctx }; - + // Store the created context object so that we can access the context // given a canvas without having to pass the parameters again. if (ctx.canvas) ctx.canvas.GLctxObject = context; @@ -6820,11 +6820,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (typeof webGLContextAttributes.enableExtensionsByDefault == 'undefined' || webGLContextAttributes.enableExtensionsByDefault) { GL.initExtensions(context); } - + return handle; }, makeContextCurrent:(contextHandle) => { - + // Active Emscripten GL layer context object. GL.currentContext = GL.contexts[contextHandle]; // Active WebGL context object. @@ -6854,15 +6854,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // If this function is called without a specific context object, init the // extensions of the currently active context. context ||= GL.currentContext; - + if (context.initExtensionsDone) return; context.initExtensionsDone = true; - + var GLctx = context.GLctx; - + // Detect the presence of a few extensions manually, since the GL interop // layer itself will need to know if they exist. - + // Extensions that are available in both WebGL 1 and WebGL 2 webgl_enable_WEBGL_multi_draw(GLctx); webgl_enable_EXT_polygon_offset_clamp(GLctx); @@ -6876,7 +6876,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { { GLctx.disjointTimerQueryExt = GLctx.getExtension('EXT_disjoint_timer_query'); } - + for (var ext of getEmscriptenSupportedExtensions(GLctx)) { // WEBGL_lose_context, WEBGL_debug_renderer_info and WEBGL_debug_shaders // are not enabled by default. @@ -6887,14 +6887,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }, }; - - + + var _eglCreateContext = (display, config, hmm, contextAttribs) => { if (display != 62000) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } - + // EGL 1.4 spec says default EGL_CONTEXT_CLIENT_VERSION is GLES1, but this is not supported by Emscripten. // So user must pass EGL_CONTEXT_CLIENT_VERSION == 2 to initialize EGL. var glesContextVersion = 1; @@ -6915,20 +6915,20 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); return 0; /* EGL_NO_CONTEXT */ } - + EGL.contextAttributes.majorVersion = glesContextVersion - 1; // WebGL 1 is GLES 2, WebGL2 is GLES3 EGL.contextAttributes.minorVersion = 0; - + EGL.context = GL.createContext(Browser.getCanvas(), EGL.contextAttributes); - + if (EGL.context != 0) { EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); - + // Run callbacks so that GL emulation works GL.makeContextCurrent(EGL.context); Browser.useWebGL = true; Browser.moduleContextCreatedCallbacks.forEach((callback) => callback()); - + // Note: This function only creates a context, but it shall not make it active. GL.makeContextCurrent(null); return 62004; @@ -6955,7 +6955,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 62006; /* Magic ID for Emscripten 'default surface' */ }; - + var _eglDestroyContext = (display, context) => { if (display != 62000) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); @@ -6965,7 +6965,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); return 0; } - + GL.deleteContext(EGL.context); EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); if (EGL.currentContext == context) { @@ -6993,7 +6993,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 1; /* Magic ID for Emscripten 'default surface' */ }; - + var _eglGetConfigAttrib = (display, config, attribute, value) => { if (display != 62000) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); @@ -7122,7 +7122,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _eglGetError = () => EGL.errorCode; - + var _eglInitialize = (display, majorVersion, minorVersion) => { if (display != 62000) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); @@ -7139,7 +7139,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 1; }; - + var _eglMakeCurrent = (display, draw, read, context) => { if (display != 62000) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); @@ -7154,9 +7154,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { EGL.setErrorCode(0x300D /* EGL_BAD_SURFACE */); return 0; } - + GL.makeContextCurrent(context ? EGL.context : null); - + EGL.currentContext = context; EGL.currentDrawSurface = draw; EGL.currentReadSurface = read; @@ -7164,15 +7164,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 1 /* EGL_TRUE */; }; - - + + var stringToNewUTF8 = (str) => { var size = lengthBytesUTF8(str) + 1; var ret = _malloc(size); if (ret) stringToUTF8(str, ret, size); return ret; }; - + var _eglQueryString = (display, name) => { if (display != 62000) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); @@ -7195,7 +7195,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return ret; }; - + var _eglSwapBuffers = (dpy, surface) => { if (!EGL.defaultDisplayInitialized) { EGL.setErrorCode(0x3001 /* EGL_NOT_INITIALIZED */); @@ -7214,10 +7214,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 0 /* EGL_FALSE */; }; - - - - + + + + /** * @param {number=} arg * @param {boolean=} noSetTiming @@ -7226,17 +7226,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { assert(!MainLoop.func, 'emscripten_set_main_loop: there can only be one main loop function at once') MainLoop.func = iterFunc; MainLoop.arg = arg; - + var thisMainLoopId = MainLoop.currentlyRunningMainloop; function checkIsRunning() { if (thisMainLoopId < MainLoop.currentlyRunningMainloop) { - + maybeExit(); return false; } return true; } - + // We create the loop runner here but it is not actually running until // _emscripten_set_main_loop_timing is called (which might happen at a // later time). This member signifies that the current runner has not @@ -7261,17 +7261,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } } MainLoop.updateStatus(); - + // catches pause/resume main loop from blocker execution if (!checkIsRunning()) return; - + setTimeout(MainLoop.runner, 0); return; } - + // catch pauses from non-main loop sources if (!checkIsRunning()) return; - + // Implement very basic swap interval control MainLoop.currentFrameNumber = MainLoop.currentFrameNumber + 1 | 0; if (MainLoop.timingMode == 1 && MainLoop.timingValue > 1 && MainLoop.currentFrameNumber % MainLoop.timingValue != 0) { @@ -7284,15 +7284,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { warnOnce('Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!'); } } - + MainLoop.runIter(iterFunc); - + // catch pauses from the main loop itself if (!checkIsRunning()) return; - + MainLoop.scheduler(); } - + if (!noSetTiming) { if (fps > 0) { _emscripten_set_main_loop_timing(0, 1000.0 / fps); @@ -7300,16 +7300,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // Do rAF by rendering each frame (no decimating) _emscripten_set_main_loop_timing(1, 1); } - + MainLoop.scheduler(); } - + if (simulateInfiniteLoop) { throw 'unwind'; } }; - - + + var MainLoop = { running:false, scheduler:null, @@ -7394,14 +7394,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_set_main_loop_timing = (mode, value) => { MainLoop.timingMode = mode; MainLoop.timingValue = value; - + if (!MainLoop.func) { err('emscripten_set_main_loop_timing: Cannot set timing mode for main loop since a main loop does not exist! Call emscripten_set_main_loop first to set one up.'); return 1; // Return non-zero on failure, can't set timing mode when there is no main loop. } - + if (!MainLoop.running) { - + MainLoop.running = true; } if (mode == 0) { @@ -7451,7 +7451,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return 0; }; - + var _eglSwapInterval = (display, interval) => { if (display != 62000) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); @@ -7459,7 +7459,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } if (interval == 0) _emscripten_set_main_loop_timing(0, 0); else _emscripten_set_main_loop_timing(1, interval); - + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); return 1; }; @@ -7477,7 +7477,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 1; }; - + var _eglWaitClient = () => { EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); return 1; @@ -7490,13 +7490,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; var readEmAsmArgsArray = []; - - - - + + + + /** @type {!Float64Array} */ var HEAPF64; - + var readEmAsmArgs = (sigPtr, buf) => { // Nobody should have mutated _readEmAsmArgsArray underneath us to be something else than an array. assert(Array.isArray(readEmAsmArgsArray)); @@ -7569,7 +7569,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { deferCall(targetFunction, precedence, argsList) { function arraysHaveEqualContent(arrA, arrB) { if (arrA.length != arrB.length) return false; - + for (var i = 0; i < arrA.length; i++) { if (arrA[i] != arrB[i]) return false; } @@ -7586,7 +7586,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { precedence, argsList }); - + JSEvents.deferredCalls.sort((x,y) => x.precedence - y.precedence); }, removeDeferredCalls(targetFunction) { @@ -7601,7 +7601,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // and https://caniuse.com/mdn-api_useractivation return navigator.userActivation.isActive; } - + return JSEvents.inEventHandler && JSEvents.currentEventHandler.allowsDeferredCalls; }, runDeferredCalls() { @@ -7648,7 +7648,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // Out of event handler - restore nesting count. --JSEvents.inEventHandler; }; - + eventHandler.target.addEventListener(eventHandler.eventTypeString, eventHandler.eventListenerFunc, eventHandler.useCapture); @@ -7691,11 +7691,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ; }, }; - + /** @type {Object} */ var specialHTMLTargets = [0, globalThis.document ?? 0, globalThis.window ?? 0]; - - + + var maybeCStringToJsString = (cString) => { // 'cString > 2' checks if the input is a number, and isn't of the special // values we accept here, EMSCRIPTEN_EVENT_TARGET_* (which map to 0, 1, 2). @@ -7703,44 +7703,44 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // memory, and points to a C string. return cString > 2 ? UTF8ToString(cString) : cString; }; - + var findEventTarget = (target) => { target = maybeCStringToJsString(target); var domElement = specialHTMLTargets[target] || globalThis.document?.querySelector(target); return domElement; }; var findCanvasEventTarget = findEventTarget; - + var _emscripten_get_canvas_element_size = (target, width, height) => { var canvas = findCanvasEventTarget(target); if (!canvas) return -4; HEAP32[((width)>>2)] = canvas.width; HEAP32[((height)>>2)] = canvas.height; }; - - - - - + + + + + var stringToUTF8OnStack = (str) => { var size = lengthBytesUTF8(str) + 1; var ret = stackAlloc(size); stringToUTF8(str, ret, size); return ret; }; - + var getCanvasElementSize = (target) => { var sp = stackSave(); var w = stackAlloc(8); var h = w + 4; - + var targetInt = stringToUTF8OnStack(target.id); var ret = _emscripten_get_canvas_element_size(targetInt, w, h); var size = [HEAP32[((w)>>2)], HEAP32[((h)>>2)]]; stackRestore(sp); return size; }; - + var _emscripten_set_canvas_element_size = (target, width, height) => { var canvas = findCanvasEventTarget(target); if (!canvas) return -4; @@ -7748,9 +7748,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { canvas.height = height; return 0; }; - - - + + + var setCanvasElementSize = (target, width, height) => { if (!target.controlTransferredOffscreen) { target.width = width; @@ -7764,12 +7764,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { stackRestore(sp); } }; - + var currentFullscreenStrategy = 0; - + var wasmTableMirror = []; - - + + var getWasmTableEntry = (funcPtr) => { var func = wasmTableMirror[funcPtr]; if (!func) { @@ -7806,15 +7806,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var oldDocumentOverflow = document.documentElement.style.overflow; // Chrome, Firefox var oldDocumentScroll = document.body.scroll; // IE var oldImageRendering = canvas.style.imageRendering; - + function restoreOldStyle() { if (!getFullscreenElement()) { document.removeEventListener('fullscreenchange', restoreOldStyle); - + document.removeEventListener('webkitfullscreenchange', restoreOldStyle); - + setCanvasElementSize(canvas, oldWidth, oldHeight); - + canvas.style.width = oldCssWidth; canvas.style.height = oldCssHeight; canvas.style.backgroundColor = oldBackgroundColor; // Chrome @@ -7836,7 +7836,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { document.body.scroll = oldDocumentScroll; // IE canvas.style.imageRendering = oldImageRendering; if (canvas.GLctxObject) canvas.GLctxObject.GLctx.viewport(0, 0, oldWidth, oldHeight); - + callCanvasResizedCallback(currentFullscreenStrategy); } } @@ -7844,15 +7844,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { document.addEventListener('webkitfullscreenchange', restoreOldStyle); return restoreOldStyle; }; - - + + var setLetterbox = (element, topBottom, leftRight) => { // Cannot use margin to specify letterboxes in FF or Chrome, since those ignore margins in fullscreen mode. element.style.paddingLeft = element.style.paddingRight = leftRight + 'px'; element.style.paddingTop = element.style.paddingBottom = topBottom + 'px'; }; - - + + var getBoundingClientRect = (e) => specialHTMLTargets.indexOf(e) < 0 ? e.getBoundingClientRect() : {'left':0,'top':0}; var JSEvents_resizeCanvasForFullscreen = (target, strategy) => { var restoreOldStyle = registerRestoreOldStyle(target); @@ -7864,7 +7864,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var canvasSize = getCanvasElementSize(target); var windowedRttWidth = canvasSize[0]; var windowedRttHeight = canvasSize[1]; - + if (strategy.scaleMode == 3) { setLetterbox(target, (cssHeight - windowedCssHeight) / 2, (cssWidth - windowedCssWidth) / 2); cssWidth = windowedCssWidth; @@ -7880,17 +7880,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { cssWidth = desiredCssWidth; } } - + // If we are adding padding, must choose a background color or otherwise Chrome will give the // padding a default white color. Do it only if user has not customized their own background color. target.style.backgroundColor ||= 'black'; // IE11 does the same, but requires the color to be set in the document body. document.body.style.backgroundColor ||= 'black'; // IE11 // Firefox always shows black letterboxes independent of style color. - + target.style.width = cssWidth + 'px'; target.style.height = cssHeight + 'px'; - + if (strategy.filteringMode == 1) { target.style.imageRendering = 'optimizeSpeed'; target.style.imageRendering = '-moz-crisp-edges'; @@ -7900,7 +7900,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { target.style.imageRendering = 'crisp-edges'; target.style.imageRendering = 'pixelated'; } - + var dpiScale = (strategy.canvasResolutionScaleMode == 2) ? devicePixelRatio : 1; if (strategy.canvasResolutionScaleMode != 0) { var newWidth = (cssWidth * dpiScale)|0; @@ -7910,13 +7910,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return restoreOldStyle; }; - + var JSEvents_requestFullscreen = (target, strategy) => { // EMSCRIPTEN_FULLSCREEN_SCALE_DEFAULT + EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE is a mode where no extra logic is performed to the DOM elements. if (strategy.scaleMode != 0 || strategy.canvasResolutionScaleMode != 0) { JSEvents_resizeCanvasForFullscreen(target, strategy); } - + if (target.requestFullscreen) { target.requestFullscreen(); } else if (target.webkitRequestFullscreen) { @@ -7926,7 +7926,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } else { return JSEvents.fullscreenEnabled() ? -3 : -1; } - + currentFullscreenStrategy = strategy; callCanvasResizedCallback(strategy); return 0; @@ -7935,7 +7935,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (!JSEvents.fullscreenEnabled()) return -1; // Make sure no queued up calls will fire after this. JSEvents.removeDeferredCalls(JSEvents_requestFullscreen); - + var d = specialHTMLTargets[1]; if (d.exitFullscreen) { d.fullscreenElement && d.exitFullscreen(); @@ -7944,11 +7944,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } else { return -1; } - + return 0; }; - + var requestPointerLock = (target) => { if (target.requestPointerLock) { target.requestPointerLock(); @@ -7970,12 +7970,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 0; }; - + var __emscripten_runtime_keepalive_clear = () => { noExitRuntime = false; runtimeKeepaliveCounter = 0; }; - + var _emscripten_force_exit = (status) => { warnOnce('emscripten_force_exit cannot actually shut down the runtime, as the build does not have EXIT_RUNTIME set'); __emscripten_runtime_keepalive_clear(); @@ -7986,23 +7986,23 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return globalThis.devicePixelRatio ?? 1.0; }; - - + + var _emscripten_get_element_css_size = (target, width, height) => { target = findEventTarget(target); if (!target) return -4; - + var rect = getBoundingClientRect(target); HEAPF64[((width)>>3)] = rect.width; HEAPF64[((height)>>3)] = rect.height; - + return 0; }; - - - - + + + + var fillGamepadEventData = (eventStruct, e) => { HEAPF64[((eventStruct)>>3)] = e.timestamp; for (var i = 0; i < e.axes.length; ++i) { @@ -8023,13 +8023,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { assert(JSEvents.lastGamepadState, 'emscripten_get_gamepad_status() called before emscripten_sample_gamepad_data()'); // INVALID_PARAM is returned on a Gamepad index that never was there. if (index < 0 || index >= JSEvents.lastGamepadState.length) return -5; - + // NO_DATA is returned on a Gamepad index that was removed. // For previously disconnected gamepads there should be an empty slot (null/undefined/false) at the index. // This is because gamepads must keep their original position in the array. // For example, removing the first of two gamepads produces [null/undefined/false, gamepad]. if (!JSEvents.lastGamepadState[index]) return -7; - + fillGamepadEventData(gamepadState, JSEvents.lastGamepadState[index]); return 0; }; @@ -8042,7 +8042,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return JSEvents.lastGamepadState.length; }; - + var _emscripten_get_screen_size = (width, height) => { HEAP32[((width)>>2)] = screen.width; HEAP32[((height)>>2)] = screen.height; @@ -8058,20 +8058,20 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.disjointTimerQueryExt['beginQueryEXT'](target, GL.queries[id]); }; - + var _emscripten_glBindAttribLocation = (program, index, name) => { GLctx.bindAttribLocation(GL.programs[program], index, UTF8ToString(name)); }; var _emscripten_glBindBuffer = (target, buffer) => { - + GLctx.bindBuffer(target, GL.buffers[buffer]); }; var _emscripten_glBindFramebuffer = (target, framebuffer) => { - + GLctx.bindFramebuffer(target, GL.framebuffers[framebuffer]); - + }; var _emscripten_glBindRenderbuffer = (target, renderbuffer) => { @@ -8082,7 +8082,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.bindTexture(target, GL.textures[texture]); }; - + var _emscripten_glBindVertexArray = (vao) => { GLctx.bindVertexArray(GL.vaos[vao]); }; @@ -8099,9 +8099,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_glBlendFuncSeparate = (x0, x1, x2, x3) => GLctx.blendFuncSeparate(x0, x1, x2, x3); - + var _emscripten_glBufferData = (target, size, data, usage) => { - + // N.b. here first form specifies a heap subarray, second form an integer // size, so the ?: code here is polymorphic. It is advised to avoid // randomly mixing both uses in calling code, to avoid any potential JS @@ -8109,11 +8109,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.bufferData(target, data ? HEAPU8.subarray(data, data+size) : size, usage); }; - + var webglBufferSubData = (target, offset, size, data, src = HEAPU8) => { GLctx.bufferSubData(target, offset, src.subarray(data, data + size)); }; - + var _emscripten_glBufferSubData = (target, offset, size, data) => webglBufferSubData(target, offset, size, data); var _emscripten_glCheckFramebufferStatus = (x0) => GLctx.checkFramebufferStatus(x0); @@ -8138,7 +8138,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.compileShader(GL.shaders[shader]); }; - + var _emscripten_glCompressedTexImage2D = (target, level, internalFormat, width, height, border, imageSize, data) => { // `data` may be null here, which means "allocate uninitialized space but // don't upload" in GLES parlance, but `compressedTexImage2D` requires the @@ -8148,7 +8148,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.compressedTexImage2D(target, level, internalFormat, width, height, border, HEAPU8.subarray(data, data + imageSize)); }; - + var _emscripten_glCompressedTexSubImage2D = (target, level, xoffset, yoffset, width, height, format, imageSize, data) => { GLctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, HEAPU8.subarray(data, data + imageSize)); }; @@ -8173,30 +8173,30 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_glCreateShader = (shaderType) => { var id = GL.getNewId(GL.shaders); GL.shaders[id] = GLctx.createShader(shaderType); - + return id; }; var _emscripten_glCullFace = (x0) => GLctx.cullFace(x0); - + var _emscripten_glDeleteBuffers = (n, buffers) => { for (var i = 0; i < n; i++) { var id = HEAP32[(((buffers)+(i*4))>>2)]; var buffer = GL.buffers[id]; - + // From spec: "glDeleteBuffers silently ignores 0's and names that do not // correspond to existing buffer objects." if (!buffer) continue; - + GLctx.deleteBuffer(buffer); buffer.name = 0; GL.buffers[id] = null; - + } }; - + var _emscripten_glDeleteFramebuffers = (n, framebuffers) => { for (var i = 0; i < n; ++i) { var id = HEAP32[(((framebuffers)+(i*4))>>2)]; @@ -8222,7 +8222,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GL.programs[id] = null; }; - + var _emscripten_glDeleteQueriesEXT = (n, ids) => { for (var i = 0; i < n; i++) { var id = HEAP32[(((ids)+(i*4))>>2)]; @@ -8233,7 +8233,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }; - + var _emscripten_glDeleteRenderbuffers = (n, renderbuffers) => { for (var i = 0; i < n; i++) { var id = HEAP32[(((renderbuffers)+(i*4))>>2)]; @@ -8258,7 +8258,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GL.shaders[id] = null; }; - + var _emscripten_glDeleteTextures = (n, textures) => { for (var i = 0; i < n; i++) { var id = HEAP32[(((textures)+(i*4))>>2)]; @@ -8272,8 +8272,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }; - - + + var _emscripten_glDeleteVertexArrays = (n, vaos) => { for (var i = 0; i < n; i++) { var id = HEAP32[(((vaos)+(i*4))>>2)]; @@ -8303,42 +8303,42 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; var _emscripten_glDrawArrays = (mode, first, count) => { - + GLctx.drawArrays(mode, first, count); - + }; - + var _emscripten_glDrawArraysInstanced = (mode, first, count, primcount) => { GLctx.drawArraysInstanced(mode, first, count, primcount); }; var _glDrawArraysInstanced = _emscripten_glDrawArraysInstanced; var _emscripten_glDrawArraysInstancedANGLE = _glDrawArraysInstanced; - + var tempFixedLengthArray = []; - - + + var _emscripten_glDrawBuffers = (n, bufs) => { - + var bufArray = tempFixedLengthArray[n]; for (var i = 0; i < n; i++) { bufArray[i] = HEAP32[(((bufs)+(i*4))>>2)]; } - + GLctx.drawBuffers(bufArray); }; var _glDrawBuffers = _emscripten_glDrawBuffers; var _emscripten_glDrawBuffersWEBGL = _glDrawBuffers; - + var _emscripten_glDrawElements = (mode, count, type, indices) => { - + GLctx.drawElements(mode, count, type, indices); - + }; - + var _emscripten_glDrawElementsInstanced = (mode, count, type, indices, primcount) => { GLctx.drawElementsInstanced(mode, count, type, indices, primcount); }; @@ -8381,7 +8381,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ); }; - + var _emscripten_glGenQueriesEXT = (n, ids) => { for (var i = 0; i < n; i++) { var query = GLctx.disjointTimerQueryExt['createQueryEXT'](); @@ -8407,7 +8407,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ); }; - + var _emscripten_glGenVertexArrays = (n, arrays) => { GL.genObject(n, arrays, 'createVertexArray', GL.vaos ); @@ -8417,8 +8417,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_glGenerateMipmap = (x0) => GLctx.generateMipmap(x0); - - + + var __glGetActiveAttribOrUniform = (funcName, program, index, bufSize, length, size, type, name) => { program = GL.programs[program]; var info = GLctx[funcName](program, index); @@ -8430,15 +8430,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (type) HEAP32[((type)>>2)] = info.type; } }; - + var _emscripten_glGetActiveAttrib = (program, index, bufSize, length, size, type, name) => __glGetActiveAttribOrUniform('getActiveAttrib', program, index, bufSize, length, size, type, name); - + var _emscripten_glGetActiveUniform = (program, index, bufSize, length, size, type, name) => __glGetActiveAttribOrUniform('getActiveUniform', program, index, bufSize, length, size, type, name); - + var _emscripten_glGetAttachedShaders = (program, maxCount, count, shaders) => { var result = GLctx.getAttachedShaders(GL.programs[program]); var len = result.length; @@ -8452,15 +8452,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }; - + var _emscripten_glGetAttribLocation = (program, name) => GLctx.getAttribLocation(GL.programs[program], UTF8ToString(name)); - + var readI53FromU64 = (ptr) => { return HEAPU32[((ptr)>>2)] + HEAPU32[(((ptr)+(4))>>2)] * 4294967296; }; - + var writeI53ToI64 = (ptr, num) => { HEAPU32[((ptr)>>2)] = num; var lower = HEAPU32[((ptr)>>2)]; @@ -8469,10 +8469,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var offset = ((ptr)>>2); if (deserialized != num) warnOnce(`writeI53ToI64() out of range: serialized JS Number ${num} to Wasm heap as bytes lo=${ptrToString(HEAPU32[offset])}, hi=${ptrToString(HEAPU32[offset+1])}, which deserializes back to ${deserialized} instead!`); }; - - - - + + + + /** @type {!Float32Array} */ var HEAPF32; var emscriptenWebGLGet = (name_, p, type) => { @@ -8508,9 +8508,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var formats = GLctx.getParameter(0x86A3 /*GL_COMPRESSED_TEXTURE_FORMATS*/); ret = formats ? formats.length : 0; break; - + } - + if (ret === undefined) { var result = GLctx.getParameter(name_); switch (typeof result) { @@ -8573,7 +8573,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return; } } - + switch (type) { case 1: writeI53ToI64(p, ret); break; case 0: HEAP32[((p)>>2)] = ret; break; @@ -8581,10 +8581,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { case 4: HEAP8[p] = ret ? 1 : 0; break; } }; - + var _emscripten_glGetBooleanv = (name_, p) => emscriptenWebGLGet(name_, p, 4); - + var _emscripten_glGetBufferParameteriv = (target, value, data) => { if (!data) { // GLES2 specification does not specify how to behave if data is a null @@ -8602,10 +8602,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return error; }; - + var _emscripten_glGetFloatv = (name_, p) => emscriptenWebGLGet(name_, p, 2); - + var _emscripten_glGetFramebufferAttachmentParameteriv = (target, attachment, pname, params) => { var result = GLctx.getFramebufferAttachmentParameter(target, attachment, pname); if (result instanceof WebGLRenderbuffer || @@ -8615,10 +8615,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP32[((params)>>2)] = result; }; - + var _emscripten_glGetIntegerv = (name_, p) => emscriptenWebGLGet(name_, p, 0); - + var _emscripten_glGetProgramInfoLog = (program, maxLength, length, infoLog) => { var log = GLctx.getProgramInfoLog(GL.programs[program]); if (log === null) log = '(unknown error)'; @@ -8626,7 +8626,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (length) HEAP32[((length)>>2)] = numBytesWrittenExclNull; }; - + var _emscripten_glGetProgramiv = (program, pname, p) => { if (!p) { // GLES2 specification does not specify how to behave if p is a null @@ -8635,14 +8635,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GL.recordError(0x501 /* GL_INVALID_VALUE */); return; } - + if (program >= GL.counter) { GL.recordError(0x501 /* GL_INVALID_VALUE */); return; } - + program = GL.programs[program]; - + if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH var log = GLctx.getProgramInfoLog(program); if (log === null) log = '(unknown error)'; @@ -8676,7 +8676,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }; - + var _emscripten_glGetQueryObjecti64vEXT = (id, pname, params) => { if (!params) { // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense @@ -8698,7 +8698,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { writeI53ToI64(params, ret); }; - + var _emscripten_glGetQueryObjectivEXT = (id, pname, params) => { if (!params) { // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense @@ -8717,15 +8717,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP32[((params)>>2)] = ret; }; - + var _glGetQueryObjecti64vEXT = _emscripten_glGetQueryObjecti64vEXT; var _emscripten_glGetQueryObjectui64vEXT = _glGetQueryObjecti64vEXT; - + var _glGetQueryObjectivEXT = _emscripten_glGetQueryObjectivEXT; var _emscripten_glGetQueryObjectuivEXT = _glGetQueryObjectivEXT; - + var _emscripten_glGetQueryivEXT = (target, pname, params) => { if (!params) { // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense @@ -8736,7 +8736,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP32[((params)>>2)] = GLctx.disjointTimerQueryExt['getQueryEXT'](target, pname); }; - + var _emscripten_glGetRenderbufferParameteriv = (target, pname, params) => { if (!params) { // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense @@ -8747,8 +8747,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP32[((params)>>2)] = GLctx.getRenderbufferParameter(target, pname); }; - - + + var _emscripten_glGetShaderInfoLog = (shader, maxLength, length, infoLog) => { var log = GLctx.getShaderInfoLog(GL.shaders[shader]); if (log === null) log = '(unknown error)'; @@ -8756,7 +8756,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (length) HEAP32[((length)>>2)] = numBytesWrittenExclNull; }; - + var _emscripten_glGetShaderPrecisionFormat = (shaderType, precisionType, range, precision) => { var result = GLctx.getShaderPrecisionFormat(shaderType, precisionType); HEAP32[((range)>>2)] = result.rangeMin; @@ -8764,7 +8764,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP32[((precision)>>2)] = result.precision; }; - + var _emscripten_glGetShaderSource = (shader, bufSize, length, source) => { var result = GLctx.getShaderSource(GL.shaders[shader]); if (!result) return; // If an error occurs, nothing will be written to length or source. @@ -8772,7 +8772,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (length) HEAP32[((length)>>2)] = numBytesWrittenExclNull; }; - + var _emscripten_glGetShaderiv = (shader, pname, p) => { if (!p) { // GLES2 specification does not specify how to behave if p is a null @@ -8801,14 +8801,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }; - - + + var webglGetExtensions = () => { var exts = getEmscriptenSupportedExtensions(GLctx); exts = exts.concat(exts.map((e) => 'GL_' + e)); return exts; }; - + var _emscripten_glGetString = (name_) => { var ret = GL.stringCache[name_]; if (!ret) { @@ -8826,7 +8826,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } ret = s ? stringToNewUTF8(s) : 0; break; - + case 0x1F02 /* GL_VERSION */: var webGLVersion = GLctx.getParameter(0x1F02 /*GL_VERSION*/); // return GLES version string corresponding to the version of the WebGL context @@ -8853,7 +8853,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return ret; }; - + var _emscripten_glGetTexParameterfv = (target, pname, params) => { if (!params) { // GLES2 specification does not specify how to behave if params is a null @@ -8865,7 +8865,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAPF32[((params)>>2)] = GLctx.getTexParameter(target, pname); }; - + var _emscripten_glGetTexParameteriv = (target, pname, params) => { if (!params) { // GLES2 specification does not specify how to behave if params is a null @@ -8879,15 +8879,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { /** @suppress {checkTypes} */ var jstoi_q = (str) => parseInt(str); - + /** @noinline */ var webglGetLeftBracePos = (name) => name.slice(-1) == ']' && name.lastIndexOf('['); - + var webglPrepareUniformLocationsBeforeFirstUse = (program) => { var uniformLocsById = program.uniformLocsById, // Maps GLuint -> WebGLUniformLocation uniformSizeAndIdsByName = program.uniformSizeAndIdsByName, // Maps name -> [uniform array length, GLuint] i, j; - + // On the first time invocation of glGetUniformLocation on this shader program: // initialize cache data structures and discover which uniforms are arrays. if (!uniformLocsById) { @@ -8895,7 +8895,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { program.uniformLocsById = uniformLocsById = {}; // maps integer locations back to uniform name strings, so that we can lazily fetch uniform array locations program.uniformArrayNamesById = {}; - + var numActiveUniforms = GLctx.getProgramParameter(program, 0x8B86/*GL_ACTIVE_UNIFORMS*/); for (i = 0; i < numActiveUniforms; ++i) { var u = GLctx.getActiveUniform(program, i); @@ -8903,7 +8903,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var sz = u.size; var lb = webglGetLeftBracePos(nm); var arrayName = lb > 0 ? nm.slice(0, lb) : nm; - + // Assign a new location. var id = program.uniformIdCounter; program.uniformIdCounter += sz; @@ -8913,7 +8913,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // application fills arrays always in full starting from the first // element of the array. uniformSizeAndIdsByName[arrayName] = [sz, id]; - + // Store placeholder integers in place that highlight that these // >0 index locations are array indices pending population. for (j = 0; j < sz; ++j) { @@ -8923,19 +8923,19 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } } }; - - - + + + var _emscripten_glGetUniformLocation = (program, name) => { - + name = UTF8ToString(name); - + if (program = GL.programs[program]) { webglPrepareUniformLocationsBeforeFirstUse(program); var uniformLocsById = program.uniformLocsById; // Maps GLuint -> WebGLUniformLocation var arrayIndex = 0; var uniformBaseName = name; - + // Invariant: when populating integer IDs for uniform locations, we must // maintain the precondition that arrays reside in contiguous addresses, // i.e. for a 'vec4 colors[10];', colors[4] must be at location @@ -8945,17 +8945,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // way to discover which uniforms are arrays is to enumerate over all the // active uniforms in the program. var leftBrace = webglGetLeftBracePos(name); - + // If user passed an array accessor "[index]", parse the array index off the accessor. if (leftBrace > 0) { arrayIndex = jstoi_q(name.slice(leftBrace + 1)) >>> 0; // "index]", coerce parseInt(']') with >>>0 to treat "foo[]" as "foo[0]" and foo[-1] as unsigned out-of-bounds. uniformBaseName = name.slice(0, leftBrace); } - + // Have we cached the location of this uniform before? // A pair [array length, GLint of the uniform location] var sizeAndId = program.uniformSizeAndIdsByName[uniformBaseName]; - + // If a uniform with this name exists, and if its index is within the // array limits (if it's even an array), query the WebGLlocation, or // return an existing cached location. @@ -8975,9 +8975,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -1; }; - + var webglGetProgramUniformLocation = (program, location) => { - + if (program) { var webglLoc = program.uniformLocsById[location]; // program.uniformLocsById[location] stores either an integer, or a @@ -8993,10 +8993,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GL.recordError(0x502/*GL_INVALID_OPERATION*/); } }; - - - - + + + + /** @suppress{checkTypes} */ var emscriptenWebGLGetUniform = (program, location, params, type) => { if (!params) { @@ -9023,17 +9023,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } } }; - + var _emscripten_glGetUniformfv = (program, location, params) => { emscriptenWebGLGetUniform(program, location, params, 2); }; - + var _emscripten_glGetUniformiv = (program, location, params) => { emscriptenWebGLGetUniform(program, location, params, 0); }; - + var _emscripten_glGetVertexAttribPointerv = (index, pname, pointer) => { if (!pointer) { // GLES2 specification does not specify how to behave if pointer is a null @@ -9045,8 +9045,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP32[((pointer)>>2)] = GLctx.getVertexAttribOffset(index, pname); }; - - + + /** @suppress{checkTypes} */ var emscriptenWebGLGetVertexAttrib = (index, pname, params, type) => { if (!params) { @@ -9075,7 +9075,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } } }; - + var _emscripten_glGetVertexAttribfv = (index, pname, params) => { // N.B. This function may only be called if the vertex attribute was // specified using the function glVertexAttrib*f(), otherwise the results @@ -9083,7 +9083,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { emscriptenWebGLGetVertexAttrib(index, pname, params, 2); }; - + var _emscripten_glGetVertexAttribiv = (index, pname, params) => { // N.B. This function may only be called if the vertex attribute was // specified using the function glVertexAttrib*f(), otherwise the results @@ -9137,9 +9137,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return GLctx.isTexture(texture); }; - + var _emscripten_glIsVertexArray = (array) => { - + var vao = GL.vaos[array]; if (!vao) return 0; return GLctx.isVertexArray(vao); @@ -9155,7 +9155,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // Invalidate earlier computed uniform->ID mappings, those have now become stale program.uniformLocsById = 0; // Mark as null-like so that glGetUniformLocation() knows to populate this again. program.uniformSizeAndIdsByName = {}; - + }; var _emscripten_glPixelStorei = (pname, param) => { @@ -9189,7 +9189,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var alignedRowSize = roundedToNextMultipleOf(plainRowSize, GL.unpackAlignment); return height * alignedRowSize; }; - + var colorChannelsInGlTextureFormat = (format) => { // Micro-optimizations for size: map format to size by subtracting smallest // enum value (0x1902) from all values first. Also omit the most common @@ -9207,43 +9207,43 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; return colorChannels[format - 0x1902]||1; }; - - - - - - + + + + + + var heapObjectForWebGLType = (type) => { // Micro-optimization for size: Subtract lowest GL enum number (0x1400/* GL_BYTE */) from type to compare // smaller values for the heap, for shorter generated code size. // Also the type HEAPU16 is not tested for explicitly, but any unrecognized type will return out HEAPU16. // (since most types are HEAPU16) type -= 0x1400; - + if (type == 1) return HEAPU8; - + if (type == 4) return HEAP32; - + if (type == 6) return HEAPF32; - + if (type == 5 || type == 28922 ) return HEAPU32; - + return HEAPU16; }; - + var toTypedArrayIndex = (pointer, heap) => pointer >>> (31 - Math.clz32(heap.BYTES_PER_ELEMENT)); - + var emscriptenWebGLGetTexPixelData = (type, format, width, height, pixels) => { var heap = heapObjectForWebGLType(type); var sizePerPixel = colorChannelsInGlTextureFormat(format) * heap.BYTES_PER_ELEMENT; var bytes = computeUnpackAlignedImageSize(width, height, sizePerPixel); return heap.subarray(toTypedArrayIndex(pixels, heap), toTypedArrayIndex(pixels + bytes, heap)); }; - + var _emscripten_glReadPixels = (x, y, width, height, format, type, pixels) => { var pixelData = emscriptenWebGLGetTexPixelData(type, format, width, height, pixels); if (!pixelData) { @@ -9271,7 +9271,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_glShaderSource = (shader, count, string, length) => { var source = GL.getSource(shader, count, string, length); - + GLctx.shaderSource(GL.shaders[shader], source); }; @@ -9287,7 +9287,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_glStencilOpSeparate = (x0, x1, x2, x3) => GLctx.stencilOpSeparate(x0, x1, x2, x3); - + var _emscripten_glTexImage2D = (target, level, internalFormat, width, height, border, format, type, pixels) => { var pixelData = pixels ? emscriptenWebGLGetTexPixelData(type, format, width, height, pixels) : null; GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixelData); @@ -9295,7 +9295,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_glTexParameterf = (x0, x1, x2) => GLctx.texParameterf(x0, x1, x2); - + var _emscripten_glTexParameterfv = (target, pname, params) => { var param = HEAPF32[((params)>>2)]; GLctx.texParameterf(target, pname, param); @@ -9303,34 +9303,34 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_glTexParameteri = (x0, x1, x2) => GLctx.texParameteri(x0, x1, x2); - + var _emscripten_glTexParameteriv = (target, pname, params) => { var param = HEAP32[((params)>>2)]; GLctx.texParameteri(target, pname, param); }; - + var _emscripten_glTexSubImage2D = (target, level, xoffset, yoffset, width, height, format, type, pixels) => { var pixelData = pixels ? emscriptenWebGLGetTexPixelData(type, format, width, height, pixels) : null; GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixelData); }; - + var webglGetUniformLocation = (location) => { - + return webglGetProgramUniformLocation(GLctx.currentProgram, location); }; - + var _emscripten_glUniform1f = (location, v0) => { GLctx.uniform1f(webglGetUniformLocation(location), v0); }; - + var miniTempWebGLFloatBuffers = []; - - + + var _emscripten_glUniform1fv = (location, count, value) => { - + if (count <= 288) { // avoid allocation when uploading few enough uniforms var view = miniTempWebGLFloatBuffers[count]; @@ -9344,17 +9344,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniform1fv(webglGetUniformLocation(location), view); }; - + var _emscripten_glUniform1i = (location, v0) => { GLctx.uniform1i(webglGetUniformLocation(location), v0); }; - + var miniTempWebGLIntBuffers = []; - - + + var _emscripten_glUniform1iv = (location, count, value) => { - + if (count <= 288) { // avoid allocation when uploading few enough uniforms var view = miniTempWebGLIntBuffers[count]; @@ -9368,16 +9368,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniform1iv(webglGetUniformLocation(location), view); }; - + var _emscripten_glUniform2f = (location, v0, v1) => { GLctx.uniform2f(webglGetUniformLocation(location), v0, v1); }; - - - + + + var _emscripten_glUniform2fv = (location, count, value) => { - + if (count <= 144) { // avoid allocation when uploading few enough uniforms count *= 2; @@ -9393,16 +9393,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniform2fv(webglGetUniformLocation(location), view); }; - + var _emscripten_glUniform2i = (location, v0, v1) => { GLctx.uniform2i(webglGetUniformLocation(location), v0, v1); }; - - - + + + var _emscripten_glUniform2iv = (location, count, value) => { - + if (count <= 144) { // avoid allocation when uploading few enough uniforms count *= 2; @@ -9418,16 +9418,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniform2iv(webglGetUniformLocation(location), view); }; - + var _emscripten_glUniform3f = (location, v0, v1, v2) => { GLctx.uniform3f(webglGetUniformLocation(location), v0, v1, v2); }; - - - + + + var _emscripten_glUniform3fv = (location, count, value) => { - + if (count <= 96) { // avoid allocation when uploading few enough uniforms count *= 3; @@ -9444,16 +9444,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniform3fv(webglGetUniformLocation(location), view); }; - + var _emscripten_glUniform3i = (location, v0, v1, v2) => { GLctx.uniform3i(webglGetUniformLocation(location), v0, v1, v2); }; - - - + + + var _emscripten_glUniform3iv = (location, count, value) => { - + if (count <= 96) { // avoid allocation when uploading few enough uniforms count *= 3; @@ -9470,16 +9470,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniform3iv(webglGetUniformLocation(location), view); }; - + var _emscripten_glUniform4f = (location, v0, v1, v2, v3) => { GLctx.uniform4f(webglGetUniformLocation(location), v0, v1, v2, v3); }; - - - + + + var _emscripten_glUniform4fv = (location, count, value) => { - + if (count <= 72) { // avoid allocation when uploading few enough uniforms var view = miniTempWebGLFloatBuffers[4*count]; @@ -9501,16 +9501,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniform4fv(webglGetUniformLocation(location), view); }; - + var _emscripten_glUniform4i = (location, v0, v1, v2, v3) => { GLctx.uniform4i(webglGetUniformLocation(location), v0, v1, v2, v3); }; - - - + + + var _emscripten_glUniform4iv = (location, count, value) => { - + if (count <= 72) { // avoid allocation when uploading few enough uniforms count *= 4; @@ -9528,11 +9528,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniform4iv(webglGetUniformLocation(location), view); }; - - - + + + var _emscripten_glUniformMatrix2fv = (location, count, transpose, value) => { - + if (count <= 72) { // avoid allocation when uploading few enough uniforms count *= 4; @@ -9550,11 +9550,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniformMatrix2fv(webglGetUniformLocation(location), !!transpose, view); }; - - - + + + var _emscripten_glUniformMatrix3fv = (location, count, transpose, value) => { - + if (count <= 32) { // avoid allocation when uploading few enough uniforms count *= 9; @@ -9577,11 +9577,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { GLctx.uniformMatrix3fv(webglGetUniformLocation(location), !!transpose, view); }; - - - + + + var _emscripten_glUniformMatrix4fv = (location, count, transpose, value) => { - + if (count <= 18) { // avoid allocation when uploading few enough uniforms var view = miniTempWebGLFloatBuffers[16*count]; @@ -9629,37 +9629,37 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_glVertexAttrib1f = (x0, x1) => GLctx.vertexAttrib1f(x0, x1); - + var _emscripten_glVertexAttrib1fv = (index, v) => { - + GLctx.vertexAttrib1f(index, HEAPF32[v>>2]); }; var _emscripten_glVertexAttrib2f = (x0, x1, x2) => GLctx.vertexAttrib2f(x0, x1, x2); - + var _emscripten_glVertexAttrib2fv = (index, v) => { - + GLctx.vertexAttrib2f(index, HEAPF32[v>>2], HEAPF32[v+4>>2]); }; var _emscripten_glVertexAttrib3f = (x0, x1, x2, x3) => GLctx.vertexAttrib3f(x0, x1, x2, x3); - + var _emscripten_glVertexAttrib3fv = (index, v) => { - + GLctx.vertexAttrib3f(index, HEAPF32[v>>2], HEAPF32[v+4>>2], HEAPF32[v+8>>2]); }; var _emscripten_glVertexAttrib4f = (x0, x1, x2, x3, x4) => GLctx.vertexAttrib4f(x0, x1, x2, x3, x4); - + var _emscripten_glVertexAttrib4fv = (index, v) => { - + GLctx.vertexAttrib4f(index, HEAPF32[v>>2], HEAPF32[v+4>>2], HEAPF32[v+8>>2], HEAPF32[v+12>>2]); }; - + var _emscripten_glVertexAttribDivisor = (index, divisor) => { GLctx.vertexAttribDivisor(index, divisor); }; @@ -9674,13 +9674,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_has_asyncify = () => 0; - - + + var doRequestFullscreen = (target, strategy) => { if (!JSEvents.fullscreenEnabled()) return -1; target = findEventTarget(target); if (!target) return -4; - + if (!target.requestFullscreen // Safari didn't Element.requestFullscreen support until 16.4 // See: https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen @@ -9688,7 +9688,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ) { return -3; } - + // Queue this function call if we're not currently in an event handler and // the user saw it appropriate to do so. if (!JSEvents.canPerformEventHandlerRequests()) { @@ -9698,10 +9698,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return -2; } - + return JSEvents_requestFullscreen(target, strategy); }; - + var _emscripten_request_fullscreen_strategy = (target, deferUntilInEventHandler, fullscreenStrategy) => { var strategy = { scaleMode: HEAP32[((fullscreenStrategy)>>2)], @@ -9711,19 +9711,19 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { canvasResizedCallback: HEAP32[(((fullscreenStrategy)+(12))>>2)], canvasResizedCallbackUserData: HEAP32[(((fullscreenStrategy)+(16))>>2)] }; - + return doRequestFullscreen(target, strategy); }; - - + + var _emscripten_request_pointerlock = (target, deferUntilInEventHandler) => { target = findEventTarget(target); if (!target) return -4; if (!target.requestPointerLock) { return -1; } - + // Queue this function call if we're not currently in an event handler and // the user saw it appropriate to do so. if (!JSEvents.canPerformEventHandlerRequests()) { @@ -9733,14 +9733,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return -2; } - + return requestPointerLock(target); }; var abortOnCannotGrowMemory = (requestedSize) => { abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ${HEAP8.length}, (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0`); }; - + var _emscripten_resize_heap = (requestedSize) => { var oldSize = HEAPU8.length; // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. @@ -9760,14 +9760,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return -1; }; - - - + + + var registerBeforeUnloadEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString) => { var beforeUnloadEventHandlerFunc = (e) => { // Note: This is always called on the main browser thread, since it needs synchronously return a value! var confirmationMessage = getWasmTableEntry(callbackfunc)(eventTypeId, 0, userData); - + if (confirmationMessage) { confirmationMessage = UTF8ToString(confirmationMessage); } @@ -9777,7 +9777,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return confirmationMessage; } }; - + var eventHandler = { target: findEventTarget(target), eventTypeString, @@ -9797,25 +9797,25 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return registerBeforeUnloadEventCallback(2, userData, true, callbackfunc, 28, 'beforeunload'); }; - - - - + + + + var registerFocusEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 256; JSEvents.focusEvent ||= _malloc(eventSize); - + var focusEventHandlerFunc = (e) => { var nodeName = JSEvents.getNodeNameForTarget(e.target); var id = e.target.id ?? ''; - + var focusEvent = JSEvents.focusEvent; stringToUTF8(nodeName, focusEvent + 0, 128); stringToUTF8(id, focusEvent + 128, 128); - + if (getWasmTableEntry(callbackfunc)(eventTypeId, focusEvent, userData)) e.preventDefault(); }; - + var eventHandler = { target: findEventTarget(target), eventTypeString, @@ -9834,22 +9834,22 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_set_element_css_size = (target, width, height) => { target = findEventTarget(target); if (!target) return -4; - + target.style.width = width + 'px'; target.style.height = height + 'px'; - + return 0; }; var _emscripten_set_focus_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => registerFocusEventCallback(target, userData, useCapture, callbackfunc, 13, 'focus', targetThread); - - - - - - + + + + + + var fillFullscreenChangeEventData = (eventStruct) => { var fullscreenElement = getFullscreenElement(); var isFullscreen = !!fullscreenElement; @@ -9872,19 +9872,19 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { JSEvents.previousFullscreenElement = fullscreenElement; } }; - - + + var registerFullscreenChangeEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 276; JSEvents.fullscreenChangeEvent ||= _malloc(eventSize); - + var fullscreenChangeEventHandlerFunc = (e) => { var fullscreenChangeEvent = JSEvents.fullscreenChangeEvent; fillFullscreenChangeEventData(fullscreenChangeEvent); - + if (getWasmTableEntry(callbackfunc)(eventTypeId, fullscreenChangeEvent, userData)) e.preventDefault(); }; - + var eventHandler = { target, eventTypeString, @@ -9896,33 +9896,33 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; return JSEvents.registerOrRemoveHandler(eventHandler); }; - + var _emscripten_set_fullscreenchange_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => { if (!JSEvents.fullscreenEnabled()) return -1; target = findEventTarget(target); if (!target) return -4; - + // TODO: When this block is removed, also change test/test_html5_remove_event_listener.c test expectation on emscripten_set_fullscreenchange_callback(). registerFullscreenChangeEventCallback(target, userData, useCapture, callbackfunc, 19, 'webkitfullscreenchange', targetThread); - + return registerFullscreenChangeEventCallback(target, userData, useCapture, callbackfunc, 19, 'fullscreenchange', targetThread); }; - - - - + + + + var registerGamepadEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 1240; JSEvents.gamepadEvent ||= _malloc(eventSize); - + var gamepadEventHandlerFunc = (e) => { var gamepadEvent = JSEvents.gamepadEvent; fillGamepadEventData(gamepadEvent, e['gamepad']); - + if (getWasmTableEntry(callbackfunc)(eventTypeId, gamepadEvent, userData)) e.preventDefault(); }; - + var eventHandler = { target: findEventTarget(target), allowsDeferredCalls: true, @@ -9935,37 +9935,37 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; return JSEvents.registerOrRemoveHandler(eventHandler); }; - + var _emscripten_set_gamepadconnected_callback_on_thread = (userData, useCapture, callbackfunc, targetThread) => { if (_emscripten_sample_gamepad_data()) return -1; return registerGamepadEventCallback(2, userData, useCapture, callbackfunc, 26, 'gamepadconnected', targetThread); }; - + var _emscripten_set_gamepaddisconnected_callback_on_thread = (userData, useCapture, callbackfunc, targetThread) => { if (_emscripten_sample_gamepad_data()) return -1; return registerGamepadEventCallback(2, userData, useCapture, callbackfunc, 27, 'gamepaddisconnected', targetThread); }; - - - - - - - + + + + + + + var registerKeyEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 160; JSEvents.keyEvent ||= _malloc(eventSize); - + var keyEventHandlerFunc = (e) => { assert(e); - + var keyEventData = JSEvents.keyEvent; HEAPF64[((keyEventData)>>3)] = e.timeStamp; - + var idx = ((keyEventData)>>2); - + HEAP32[idx + 2] = e.location; HEAP8[keyEventData + 12] = e.ctrlKey; HEAP8[keyEventData + 13] = e.shiftKey; @@ -9979,10 +9979,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { stringToUTF8(e.code ?? '', keyEventData + 64, 32); stringToUTF8(e.char ?? '', keyEventData + 96, 32); stringToUTF8(e.locale ?? '', keyEventData + 128, 32); - + if (getWasmTableEntry(callbackfunc)(eventTypeId, keyEventData, userData)) e.preventDefault(); }; - + var eventHandler = { target: findEventTarget(target), eventTypeString, @@ -10003,17 +10003,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_set_keyup_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => registerKeyEventCallback(target, userData, useCapture, callbackfunc, 3, 'keyup', targetThread); - + var _emscripten_set_main_loop = (func, fps, simulateInfiniteLoop) => { var iterFunc = getWasmTableEntry(func); setMainLoop(iterFunc, fps, simulateInfiniteLoop); }; - - - - - + + + + + var fillMouseEventData = (eventStruct, e, target) => { assert(eventStruct % 4 == 0); HEAPF64[((eventStruct)>>3)] = e.timeStamp; @@ -10030,27 +10030,27 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP16[idx*2 + 15] = e.buttons; HEAP32[idx + 8] = e.movementX; HEAP32[idx + 9] = e.movementY; - + // Note: rect contains doubles (truncated to placate SAFE_HEAP, which is the same behaviour when writing to HEAP32 anyway) var rect = getBoundingClientRect(target); HEAP32[idx + 10] = e.clientX - (rect.left | 0); HEAP32[idx + 11] = e.clientY - (rect.top | 0); }; - - - + + + var registerMouseEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 64; JSEvents.mouseEvent ||= _malloc(eventSize); target = findEventTarget(target); - + var mouseEventHandlerFunc = (e) => { // TODO: Make this access thread safe, or this could update live while app is reading it. fillMouseEventData(JSEvents.mouseEvent, e, target); - + if (getWasmTableEntry(callbackfunc)(eventTypeId, JSEvents.mouseEvent, userData)) e.preventDefault(); }; - + var eventHandler = { target, allowsDeferredCalls: eventTypeString != 'mousemove' && eventTypeString != 'mouseenter' && eventTypeString != 'mouseleave', // Mouse move events do not allow fullscreen/pointer lock requests to be handled in them! @@ -10078,10 +10078,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_set_mouseup_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => registerMouseEventCallback(target, userData, useCapture, callbackfunc, 6, 'mouseup', targetThread); - - - - + + + + var fillPointerlockChangeEventData = (eventStruct) => { var pointerLockElement = document.pointerLockElement; var isPointerlocked = !!pointerLockElement; @@ -10093,19 +10093,19 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { stringToUTF8(nodeName, eventStruct + 1, 128); stringToUTF8(id, eventStruct + 129, 128); }; - - + + var registerPointerlockChangeEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 257; JSEvents.pointerlockChangeEvent ||= _malloc(eventSize); - + var pointerlockChangeEventHandlerFunc = (e) => { var pointerlockChangeEvent = JSEvents.pointerlockChangeEvent; fillPointerlockChangeEventData(pointerlockChangeEvent); - + if (getWasmTableEntry(callbackfunc)(eventTypeId, pointerlockChangeEvent, userData)) e.preventDefault(); }; - + var eventHandler = { target, eventTypeString, @@ -10117,27 +10117,27 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; return JSEvents.registerOrRemoveHandler(eventHandler); }; - + var _emscripten_set_pointerlockchange_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => { if (!document.body?.requestPointerLock) { return -1; } - + target = findEventTarget(target); if (!target) return -4; return registerPointerlockChangeEventCallback(target, userData, useCapture, callbackfunc, 20, 'pointerlockchange', targetThread); }; - - - - + + + + var registerUiEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 36; JSEvents.uiEvent ||= _malloc(eventSize); - + target = findEventTarget(target); - + var uiEventHandlerFunc = (e) => { if (e.target != target) { // Never take ui events such as scroll via a 'bubbled' route, but always from the direct element that @@ -10163,7 +10163,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP32[(((uiEvent)+(32))>>2)] = pageYOffset | 0; if (getWasmTableEntry(callbackfunc)(eventTypeId, uiEvent, userData)) e.preventDefault(); }; - + var eventHandler = { target, eventTypeString, @@ -10178,26 +10178,26 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_set_resize_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => registerUiEventCallback(target, userData, useCapture, callbackfunc, 10, 'resize', targetThread); - - - - - - - + + + + + + + var registerTouchEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 1552; JSEvents.touchEvent ||= _malloc(eventSize); - + target = findEventTarget(target); - + var touchEventHandlerFunc = (e) => { assert(e); var t, touches = {}, et = e.touches; // To ease marshalling different kinds of touches that browser reports (all touches are listed in e.touches, // only changed touches in e.changedTouches, and touches on target at a.targetTouches), mark a boolean in // each Touch object so that we can later loop only once over all touches we see to marshall over to Wasm. - + for (let t of et) { // Browser might recycle the generated Touch objects between each frame (Firefox on Android), so reset any // changed/target states we may have set from previous frame. @@ -10213,7 +10213,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { for (let t of e.targetTouches) { touches[t.identifier].onTarget = 1; } - + var touchEvent = JSEvents.touchEvent; HEAPF64[((touchEvent)>>3)] = e.timeStamp; HEAP8[touchEvent + 12] = e.ctrlKey; @@ -10236,18 +10236,18 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP8[idx + 29] = t.onTarget; HEAP32[idx32 + 8] = t.clientX - (targetRect.left | 0); HEAP32[idx32 + 9] = t.clientY - (targetRect.top | 0); - + idx += 48; - + if (++numTouches > 31) { break; } } HEAP32[(((touchEvent)+(8))>>2)] = numTouches; - + if (getWasmTableEntry(callbackfunc)(eventTypeId, touchEvent, userData)) e.preventDefault(); }; - + var eventHandler = { target, allowsDeferredCalls: eventTypeString == 'touchstart' || eventTypeString == 'touchend', @@ -10272,30 +10272,30 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var _emscripten_set_touchstart_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => registerTouchEventCallback(target, userData, useCapture, callbackfunc, 22, 'touchstart', targetThread); - - + + var fillVisibilityChangeEventData = (eventStruct) => { var visibilityStates = [ 'hidden', 'visible', 'prerender', 'unloaded' ]; var visibilityState = visibilityStates.indexOf(document.visibilityState); - + // Assigning a boolean to HEAP32 with expected type coercion. /** @suppress{checkTypes} */ HEAP8[eventStruct] = document.hidden; HEAP32[(((eventStruct)+(4))>>2)] = visibilityState; }; - - + + var registerVisibilityChangeEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 8; JSEvents.visibilityChangeEvent ||= _malloc(eventSize); - + var visibilityChangeEventHandlerFunc = (e) => { var visibilityChangeEvent = JSEvents.visibilityChangeEvent; fillVisibilityChangeEventData(visibilityChangeEvent); - + if (getWasmTableEntry(callbackfunc)(eventTypeId, visibilityChangeEvent, userData)) e.preventDefault(); }; - + var eventHandler = { target, eventTypeString, @@ -10307,7 +10307,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; return JSEvents.registerOrRemoveHandler(eventHandler); }; - + var _emscripten_set_visibilitychange_callback_on_thread = (userData, useCapture, callbackfunc, targetThread) => { if (!specialHTMLTargets[1]) { return -4; @@ -10315,15 +10315,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return registerVisibilityChangeEventCallback(specialHTMLTargets[1], userData, useCapture, callbackfunc, 21, 'visibilitychange', targetThread); }; - - - - - + + + + + var registerWheelEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { var eventSize = 96; JSEvents.wheelEvent ||= _malloc(eventSize) - + // The DOM Level 3 events spec event 'wheel' var wheelHandlerFunc = (e) => { var wheelEvent = JSEvents.wheelEvent; @@ -10334,7 +10334,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { HEAP32[(((wheelEvent)+(88))>>2)] = e["deltaMode"]; if (getWasmTableEntry(callbackfunc)(eventTypeId, wheelEvent, userData)) e.preventDefault(); }; - + var eventHandler = { target, allowsDeferredCalls: true, @@ -10347,7 +10347,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; return JSEvents.registerOrRemoveHandler(eventHandler); }; - + var _emscripten_set_wheel_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => { target = findEventTarget(target); if (!target) return -4; @@ -10358,7 +10358,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }; - + var _emscripten_set_window_title = (title) => document.title = UTF8ToString(title); var _emscripten_sleep = () => { @@ -10367,7 +10367,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var ENV = { }; - + var getExecutableName = () => thisProgram; var getEnvStrings = () => { if (!getEnvStrings.strings) { @@ -10398,8 +10398,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return getEnvStrings.strings; }; - - + + var _environ_get = (__environ, environ_buf) => { var bufSize = 0; var envp = 0; @@ -10412,8 +10412,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return 0; }; - - + + var _environ_sizes_get = (penviron_count, penviron_buf_size) => { var strings = getEnvStrings(); HEAPU32[((penviron_count)>>2)] = strings.length; @@ -10428,7 +10428,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { function _fd_close(fd) { try { - + var stream = SYSCALLS.getStreamFromFD(fd); FS.close(stream); return 0; @@ -10437,9 +10437,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return e.errno; } } - - + + /** @param {number=} offset */ var doReadv = (stream, iov, iovcnt, offset) => { var ret = 0; @@ -10468,11 +10468,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return ret; }; - - + + function _fd_read(fd, iov, iovcnt, pnum) { try { - + var stream = SYSCALLS.getStreamFromFD(fd); var num = doReadv(stream, iov, iovcnt); HEAPU32[((pnum)>>2)] = num; @@ -10482,16 +10482,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return e.errno; } } - - - + + + function _fd_seek(fd, offset, whence, newOffset) { offset = bigintToI53Checked(offset); - - + + try { - + if (isNaN(offset)) return 22; var stream = SYSCALLS.getStreamFromFD(fd); FS.llseek(stream, offset, whence); @@ -10505,8 +10505,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { ; } - - + + /** @param {number=} offset */ var doWritev = (stream, iov, iovcnt, offset) => { // Gather all iovecs into one contiguous buffer and issue a single @@ -10531,11 +10531,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return FS.write(stream, view, 0, total, offset); }; - - + + function _fd_write(fd, iov, iovcnt, pnum) { try { - + var stream = SYSCALLS.getStreamFromFD(fd); var num = doWritev(stream, iov, iovcnt); HEAPU32[((pnum)>>2)] = num; @@ -10545,10 +10545,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return e.errno; } } - - + + var _random_get = (buffer, size) => randomFill(HEAPU8.subarray(buffer, buffer + size)); @@ -10571,11 +10571,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { assert(getWasmTableEntry(ptr), `missing table entry in dynCall: ${ptr}`); var func = getWasmTableEntry(ptr); var rtn = func(...args); - + function convert(rtn) { return rtn; } - + return convert(rtn); }; @@ -10583,13 +10583,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { - - - - - - - + + + + + + + + /** * @param {number} ptr * @param {number} value @@ -11140,24 +11141,25 @@ function checkIncomingModuleAPI() { ignoredModuleProp('wasmBinary'); } var ASM_CONSTS = { - 324308: () => { }, - 324309: ($0) => { var str = UTF8ToString($0) + '\n\n' + 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :'; var reply = window.prompt(str, "i"); if (reply === null) { reply = "i"; } return reply.length === 1 ? reply.charCodeAt(0) : -1; }, - 324524: () => { if (typeof(AudioContext) !== 'undefined') { return true; } else if (typeof(webkitAudioContext) !== 'undefined') { return true; } return false; }, - 324671: () => { if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) { return true; } else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') { return true; } return false; }, - 324905: ($0) => { if(typeof(Module['SDL2']) === 'undefined') { Module['SDL2'] = {}; } var SDL2 = Module['SDL2']; if (!$0) { SDL2.audio = {}; } else { SDL2.capture = {}; } if (!SDL2.audioContext) { if (typeof(AudioContext) !== 'undefined') { SDL2.audioContext = new AudioContext(); } else if (typeof(webkitAudioContext) !== 'undefined') { SDL2.audioContext = new webkitAudioContext(); } if (SDL2.audioContext) { if ((typeof navigator.userActivation) === 'undefined') { autoResumeAudioContext(SDL2.audioContext); } } } return SDL2.audioContext === undefined ? -1 : 0; }, - 325457: () => { var SDL2 = Module['SDL2']; return SDL2.audioContext.sampleRate; }, - 325525: ($0, $1, $2, $3) => { var SDL2 = Module['SDL2']; var have_microphone = function(stream) { if (SDL2.capture.silenceTimer !== undefined) { clearInterval(SDL2.capture.silenceTimer); SDL2.capture.silenceTimer = undefined; SDL2.capture.silenceBuffer = undefined } SDL2.capture.mediaStreamNode = SDL2.audioContext.createMediaStreamSource(stream); SDL2.capture.scriptProcessorNode = SDL2.audioContext.createScriptProcessor($1, $0, 1); SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) { if ((SDL2 === undefined) || (SDL2.capture === undefined)) { return; } audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0); SDL2.capture.currentCaptureBuffer = audioProcessingEvent.inputBuffer; dynCall('vp', $2, [$3]); }; SDL2.capture.mediaStreamNode.connect(SDL2.capture.scriptProcessorNode); SDL2.capture.scriptProcessorNode.connect(SDL2.audioContext.destination); SDL2.capture.stream = stream; }; var no_microphone = function(error) { }; SDL2.capture.silenceBuffer = SDL2.audioContext.createBuffer($0, $1, SDL2.audioContext.sampleRate); SDL2.capture.silenceBuffer.getChannelData(0).fill(0.0); var silence_callback = function() { SDL2.capture.currentCaptureBuffer = SDL2.capture.silenceBuffer; dynCall('vp', $2, [$3]); }; SDL2.capture.silenceTimer = setInterval(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000); if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) { navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone); } else if (navigator.webkitGetUserMedia !== undefined) { navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone); } }, - 327218: ($0, $1, $2, $3) => { var SDL2 = Module['SDL2']; SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0); SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) { if ((SDL2 === undefined) || (SDL2.audio === undefined)) { return; } if (SDL2.audio.silenceTimer !== undefined) { clearInterval(SDL2.audio.silenceTimer); SDL2.audio.silenceTimer = undefined; SDL2.audio.silenceBuffer = undefined; } SDL2.audio.currentOutputBuffer = e['outputBuffer']; dynCall('vp', $2, [$3]); }; SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']); if (SDL2.audioContext.state === 'suspended') { SDL2.audio.silenceBuffer = SDL2.audioContext.createBuffer($0, $1, SDL2.audioContext.sampleRate); SDL2.audio.silenceBuffer.getChannelData(0).fill(0.0); var silence_callback = function() { if ((typeof navigator.userActivation) !== 'undefined') { if (navigator.userActivation.hasBeenActive) { SDL2.audioContext.resume(); } } SDL2.audio.currentOutputBuffer = SDL2.audio.silenceBuffer; dynCall('vp', $2, [$3]); SDL2.audio.currentOutputBuffer = undefined; }; SDL2.audio.silenceTimer = setInterval(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000); } }, - 328393: ($0, $1) => { var SDL2 = Module['SDL2']; var numChannels = SDL2.capture.currentCaptureBuffer.numberOfChannels; for (var c = 0; c < numChannels; ++c) { var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c); if (channelData.length != $1) { throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; } if (numChannels == 1) { for (var j = 0; j < $1; ++j) { setValue($0 + (j * 4), channelData[j], 'float'); } } else { for (var j = 0; j < $1; ++j) { setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float'); } } } }, - 328998: ($0, $1) => { var SDL2 = Module['SDL2']; var buf = $0 >>> 2; var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels']; for (var c = 0; c < numChannels; ++c) { var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c); if (channelData.length != $1) { throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; } for (var j = 0; j < $1; ++j) { channelData[j] = HEAPF32[buf + (j*numChannels + c)]; } } }, - 329487: ($0) => { var SDL2 = Module['SDL2']; if ($0) { if (SDL2.capture.silenceTimer !== undefined) { clearInterval(SDL2.capture.silenceTimer); } if (SDL2.capture.stream !== undefined) { var tracks = SDL2.capture.stream.getAudioTracks(); for (var i = 0; i < tracks.length; i++) { SDL2.capture.stream.removeTrack(tracks[i]); } } if (SDL2.capture.scriptProcessorNode !== undefined) { SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {}; SDL2.capture.scriptProcessorNode.disconnect(); } if (SDL2.capture.mediaStreamNode !== undefined) { SDL2.capture.mediaStreamNode.disconnect(); } SDL2.capture = undefined; } else { if (SDL2.audio.scriptProcessorNode != undefined) { SDL2.audio.scriptProcessorNode.disconnect(); } if (SDL2.audio.silenceTimer !== undefined) { clearInterval(SDL2.audio.silenceTimer); } SDL2.audio = undefined; } if ((SDL2.audioContext !== undefined) && (SDL2.audio === undefined) && (SDL2.capture === undefined)) { SDL2.audioContext.close(); SDL2.audioContext = undefined; } }, - 330493: ($0, $1, $2) => { var w = $0; var h = $1; var pixels = $2; if (!Module['SDL2']) Module['SDL2'] = {}; var SDL2 = Module['SDL2']; if (SDL2.ctxCanvas !== Module['canvas']) { SDL2.ctx = Browser.createContext(Module['canvas'], false, true); SDL2.ctxCanvas = Module['canvas']; } if (SDL2.w !== w || SDL2.h !== h || SDL2.imageCtx !== SDL2.ctx) { SDL2.image = SDL2.ctx.createImageData(w, h); SDL2.w = w; SDL2.h = h; SDL2.imageCtx = SDL2.ctx; } var data = SDL2.image.data; var src = pixels / 4; var dst = 0; var num; if (typeof CanvasPixelArray !== 'undefined' && data instanceof CanvasPixelArray) { num = data.length; while (dst < num) { var val = HEAP32[src]; data[dst ] = val & 0xff; data[dst+1] = (val >> 8) & 0xff; data[dst+2] = (val >> 16) & 0xff; data[dst+3] = 0xff; src++; dst += 4; } } else { if (SDL2.data32Data !== data) { SDL2.data32 = new Int32Array(data.buffer); SDL2.data8 = new Uint8Array(data.buffer); SDL2.data32Data = data; } var data32 = SDL2.data32; num = data32.length; data32.set(HEAP32.subarray(src, src + num)); var data8 = SDL2.data8; var i = 3; var j = i + 4*num; if (num % 8 == 0) { while (i < j) { data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; } } else { while (i < j) { data8[i] = 0xff; i = i + 4 | 0; } } } SDL2.ctx.putImageData(SDL2.image, 0, 0); }, - 331959: ($0, $1, $2, $3, $4) => { var w = $0; var h = $1; var hot_x = $2; var hot_y = $3; var pixels = $4; var canvas = document.createElement("canvas"); canvas.width = w; canvas.height = h; var ctx = canvas.getContext("2d"); var image = ctx.createImageData(w, h); var data = image.data; var src = pixels / 4; var dst = 0; var num; if (typeof CanvasPixelArray !== 'undefined' && data instanceof CanvasPixelArray) { num = data.length; while (dst < num) { var val = HEAP32[src]; data[dst ] = val & 0xff; data[dst+1] = (val >> 8) & 0xff; data[dst+2] = (val >> 16) & 0xff; data[dst+3] = (val >> 24) & 0xff; src++; dst += 4; } } else { var data32 = new Int32Array(data.buffer); num = data32.length; data32.set(HEAP32.subarray(src, src + num)); } ctx.putImageData(image, 0, 0); var url = hot_x === 0 && hot_y === 0 ? "url(" + canvas.toDataURL() + "), auto" : "url(" + canvas.toDataURL() + ") " + hot_x + " " + hot_y + ", auto"; var urlBuf = _malloc(url.length + 1); stringToUTF8(url, urlBuf, url.length + 1); return urlBuf; }, - 332947: ($0) => { if (Module['canvas']) { Module['canvas'].style['cursor'] = UTF8ToString($0); } }, - 333030: () => { if (Module['canvas']) { Module['canvas'].style['cursor'] = 'none'; } }, - 333099: () => { return window.innerWidth; }, - 333129: () => { return window.innerHeight; }, - 333160: ($0, $1) => { alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1)); } + 324404: () => { }, + 324405: ($0) => { console.log("BOXEDWINE_FILE:" + UTF8ToString($0)); window.parent.postMessage({ type: "boxedwine-file-read", path: UTF8ToString($0) }, window.location.origin); }, + 324568: ($0) => { var str = UTF8ToString($0) + '\n\n' + 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :'; var reply = window.prompt(str, "i"); if (reply === null) { reply = "i"; } return reply.length === 1 ? reply.charCodeAt(0) : -1; }, + 324783: () => { if (typeof(AudioContext) !== 'undefined') { return true; } else if (typeof(webkitAudioContext) !== 'undefined') { return true; } return false; }, + 324930: () => { if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) { return true; } else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') { return true; } return false; }, + 325164: ($0) => { if(typeof(Module['SDL2']) === 'undefined') { Module['SDL2'] = {}; } var SDL2 = Module['SDL2']; if (!$0) { SDL2.audio = {}; } else { SDL2.capture = {}; } if (!SDL2.audioContext) { if (typeof(AudioContext) !== 'undefined') { SDL2.audioContext = new AudioContext(); } else if (typeof(webkitAudioContext) !== 'undefined') { SDL2.audioContext = new webkitAudioContext(); } if (SDL2.audioContext) { if ((typeof navigator.userActivation) === 'undefined') { autoResumeAudioContext(SDL2.audioContext); } } } return SDL2.audioContext === undefined ? -1 : 0; }, + 325716: () => { var SDL2 = Module['SDL2']; return SDL2.audioContext.sampleRate; }, + 325784: ($0, $1, $2, $3) => { var SDL2 = Module['SDL2']; var have_microphone = function(stream) { if (SDL2.capture.silenceTimer !== undefined) { clearInterval(SDL2.capture.silenceTimer); SDL2.capture.silenceTimer = undefined; SDL2.capture.silenceBuffer = undefined } SDL2.capture.mediaStreamNode = SDL2.audioContext.createMediaStreamSource(stream); SDL2.capture.scriptProcessorNode = SDL2.audioContext.createScriptProcessor($1, $0, 1); SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) { if ((SDL2 === undefined) || (SDL2.capture === undefined)) { return; } audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0); SDL2.capture.currentCaptureBuffer = audioProcessingEvent.inputBuffer; dynCall('vp', $2, [$3]); }; SDL2.capture.mediaStreamNode.connect(SDL2.capture.scriptProcessorNode); SDL2.capture.scriptProcessorNode.connect(SDL2.audioContext.destination); SDL2.capture.stream = stream; }; var no_microphone = function(error) { }; SDL2.capture.silenceBuffer = SDL2.audioContext.createBuffer($0, $1, SDL2.audioContext.sampleRate); SDL2.capture.silenceBuffer.getChannelData(0).fill(0.0); var silence_callback = function() { SDL2.capture.currentCaptureBuffer = SDL2.capture.silenceBuffer; dynCall('vp', $2, [$3]); }; SDL2.capture.silenceTimer = setInterval(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000); if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) { navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone); } else if (navigator.webkitGetUserMedia !== undefined) { navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone); } }, + 327477: ($0, $1, $2, $3) => { var SDL2 = Module['SDL2']; SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0); SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) { if ((SDL2 === undefined) || (SDL2.audio === undefined)) { return; } if (SDL2.audio.silenceTimer !== undefined) { clearInterval(SDL2.audio.silenceTimer); SDL2.audio.silenceTimer = undefined; SDL2.audio.silenceBuffer = undefined; } SDL2.audio.currentOutputBuffer = e['outputBuffer']; dynCall('vp', $2, [$3]); }; SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']); if (SDL2.audioContext.state === 'suspended') { SDL2.audio.silenceBuffer = SDL2.audioContext.createBuffer($0, $1, SDL2.audioContext.sampleRate); SDL2.audio.silenceBuffer.getChannelData(0).fill(0.0); var silence_callback = function() { if ((typeof navigator.userActivation) !== 'undefined') { if (navigator.userActivation.hasBeenActive) { SDL2.audioContext.resume(); } } SDL2.audio.currentOutputBuffer = SDL2.audio.silenceBuffer; dynCall('vp', $2, [$3]); SDL2.audio.currentOutputBuffer = undefined; }; SDL2.audio.silenceTimer = setInterval(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000); } }, + 328652: ($0, $1) => { var SDL2 = Module['SDL2']; var numChannels = SDL2.capture.currentCaptureBuffer.numberOfChannels; for (var c = 0; c < numChannels; ++c) { var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c); if (channelData.length != $1) { throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; } if (numChannels == 1) { for (var j = 0; j < $1; ++j) { setValue($0 + (j * 4), channelData[j], 'float'); } } else { for (var j = 0; j < $1; ++j) { setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float'); } } } }, + 329257: ($0, $1) => { var SDL2 = Module['SDL2']; var buf = $0 >>> 2; var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels']; for (var c = 0; c < numChannels; ++c) { var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c); if (channelData.length != $1) { throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; } for (var j = 0; j < $1; ++j) { channelData[j] = HEAPF32[buf + (j*numChannels + c)]; } } }, + 329746: ($0) => { var SDL2 = Module['SDL2']; if ($0) { if (SDL2.capture.silenceTimer !== undefined) { clearInterval(SDL2.capture.silenceTimer); } if (SDL2.capture.stream !== undefined) { var tracks = SDL2.capture.stream.getAudioTracks(); for (var i = 0; i < tracks.length; i++) { SDL2.capture.stream.removeTrack(tracks[i]); } } if (SDL2.capture.scriptProcessorNode !== undefined) { SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {}; SDL2.capture.scriptProcessorNode.disconnect(); } if (SDL2.capture.mediaStreamNode !== undefined) { SDL2.capture.mediaStreamNode.disconnect(); } SDL2.capture = undefined; } else { if (SDL2.audio.scriptProcessorNode != undefined) { SDL2.audio.scriptProcessorNode.disconnect(); } if (SDL2.audio.silenceTimer !== undefined) { clearInterval(SDL2.audio.silenceTimer); } SDL2.audio = undefined; } if ((SDL2.audioContext !== undefined) && (SDL2.audio === undefined) && (SDL2.capture === undefined)) { SDL2.audioContext.close(); SDL2.audioContext = undefined; } }, + 330752: ($0, $1, $2) => { var w = $0; var h = $1; var pixels = $2; if (!Module['SDL2']) Module['SDL2'] = {}; var SDL2 = Module['SDL2']; if (SDL2.ctxCanvas !== Module['canvas']) { SDL2.ctx = Browser.createContext(Module['canvas'], false, true); SDL2.ctxCanvas = Module['canvas']; } if (SDL2.w !== w || SDL2.h !== h || SDL2.imageCtx !== SDL2.ctx) { SDL2.image = SDL2.ctx.createImageData(w, h); SDL2.w = w; SDL2.h = h; SDL2.imageCtx = SDL2.ctx; } var data = SDL2.image.data; var src = pixels / 4; var dst = 0; var num; if (typeof CanvasPixelArray !== 'undefined' && data instanceof CanvasPixelArray) { num = data.length; while (dst < num) { var val = HEAP32[src]; data[dst ] = val & 0xff; data[dst+1] = (val >> 8) & 0xff; data[dst+2] = (val >> 16) & 0xff; data[dst+3] = 0xff; src++; dst += 4; } } else { if (SDL2.data32Data !== data) { SDL2.data32 = new Int32Array(data.buffer); SDL2.data8 = new Uint8Array(data.buffer); SDL2.data32Data = data; } var data32 = SDL2.data32; num = data32.length; data32.set(HEAP32.subarray(src, src + num)); var data8 = SDL2.data8; var i = 3; var j = i + 4*num; if (num % 8 == 0) { while (i < j) { data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; } } else { while (i < j) { data8[i] = 0xff; i = i + 4 | 0; } } } SDL2.ctx.putImageData(SDL2.image, 0, 0); }, + 332218: ($0, $1, $2, $3, $4) => { var w = $0; var h = $1; var hot_x = $2; var hot_y = $3; var pixels = $4; var canvas = document.createElement("canvas"); canvas.width = w; canvas.height = h; var ctx = canvas.getContext("2d"); var image = ctx.createImageData(w, h); var data = image.data; var src = pixels / 4; var dst = 0; var num; if (typeof CanvasPixelArray !== 'undefined' && data instanceof CanvasPixelArray) { num = data.length; while (dst < num) { var val = HEAP32[src]; data[dst ] = val & 0xff; data[dst+1] = (val >> 8) & 0xff; data[dst+2] = (val >> 16) & 0xff; data[dst+3] = (val >> 24) & 0xff; src++; dst += 4; } } else { var data32 = new Int32Array(data.buffer); num = data32.length; data32.set(HEAP32.subarray(src, src + num)); } ctx.putImageData(image, 0, 0); var url = hot_x === 0 && hot_y === 0 ? "url(" + canvas.toDataURL() + "), auto" : "url(" + canvas.toDataURL() + ") " + hot_x + " " + hot_y + ", auto"; var urlBuf = _malloc(url.length + 1); stringToUTF8(url, urlBuf, url.length + 1); return urlBuf; }, + 333206: ($0) => { if (Module['canvas']) { Module['canvas'].style['cursor'] = UTF8ToString($0); } }, + 333289: () => { if (Module['canvas']) { Module['canvas'].style['cursor'] = 'none'; } }, + 333358: () => { return window.innerWidth; }, + 333388: () => { return window.innerHeight; }, + 333419: ($0, $1) => { alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1)); } }; // Imports from the Wasm binary. diff --git a/site/vendor/boxedwine/26R1/boxedwine.wasm b/site/vendor/boxedwine/26R1/boxedwine.wasm index 5185a23d..3690cc25 100644 Binary files a/site/vendor/boxedwine/26R1/boxedwine.wasm and b/site/vendor/boxedwine/26R1/boxedwine.wasm differ diff --git a/site/vendor/boxedwine/26R1/index.html b/site/vendor/boxedwine/26R1/index.html index 62bedfcc..e5db2727 100644 --- a/site/vendor/boxedwine/26R1/index.html +++ b/site/vendor/boxedwine/26R1/index.html @@ -57,9 +57,12 @@ ? appRoot.href : `${appRoot.href}/`, archive: value("archive", /^[a-z0-9][a-z0-9-]*$/), + root: value("root", /^[a-z0-9][a-z0-9-]*$/), executable: value("executable", /^[a-z0-9_. -]+\.exe$/i), resolution: value("resolution", /^\d{3,4}x\d{3,4}$/), sound: value("sound", /^(?:true|false)$/), + cache: value("cache", /^(?:true|false)$/), + trace: value("trace", /^(?:true|false)$/), }); })(); @@ -97,17 +100,20 @@ > + ', '', '', ].join("\n"), - "apps/core/boxedwine.js": + "apps/core/boxedwine.js": [ + 'const ROOT_ARCHIVE = "xp-accessories";', "new URL(`${RUNTIME_ROOT}index.html`, document.baseURI);", + ].join("\n"), "swf/bike-mania/main.swf": "swf", "iframe/doom/index.html": "doom ../../dos/doom/doom.jsdos", "iframe/inside-the-firewall/index.html": "firewall", @@ -330,8 +346,12 @@ describe("build metadata", () => { const boxedWineShell = boxedWineRunnerMarkup.match( /boxedwine-shell\.[a-f0-9]{8}\.js/, )?.[0]; + const boxedWineRoot = boxedWineIndex.match( + /xp-accessories-[a-f0-9]{8}/, + )?.[0]; expect(boxedWineJavaScript).toBeDefined(); expect(boxedWineShell).toBeDefined(); + expect(boxedWineRoot).toBeDefined(); expect( await Bun.file( join(root, "vendor", "boxedwine", "26R1", boxedWineJavaScript!), @@ -342,12 +362,40 @@ describe("build metadata", () => { join(root, "vendor", "boxedwine", "26R1", boxedWineShell!), ).exists(), ).toBeTrue(); + const boxedWineJavaScriptSource = await readFile( + join(root, "vendor", "boxedwine", "26R1", boxedWineJavaScript!), + "utf8", + ); + expect(boxedWineJavaScriptSource).toMatch( + /locateFile\('boxedwine\.[a-f0-9]{8}\.wasm'\)/, + ); + const boxedWineWasm = boxedWineJavaScriptSource.match( + /boxedwine\.[a-f0-9]{8}\.wasm/, + )?.[0]; expect( + await Bun.file( + join(root, "vendor", "boxedwine", "26R1", `${boxedWineRoot}.zip`), + ).exists(), + ).toBeTrue(); + expect( + await Bun.file( + join(root, "vendor", "boxedwine", "26R1", "boxedwine.zip"), + ).exists(), + ).toBeFalse(); + const boxedWinePreload = JSON.parse( await readFile( - join(root, "vendor", "boxedwine", "26R1", boxedWineJavaScript!), + join(root, "vendor", "boxedwine", "26R1", "preload.json"), "utf8", ), - ).toMatch(/locateFile\('boxedwine\.[a-f0-9]{8}\.wasm'\)/); + ); + expect(boxedWinePreload.files).toEqual([ + "boxedwine-startup.js", + boxedWineShell, + boxedWineJavaScript, + boxedWineWasm, + boxedWineRunner, + `${boxedWineRoot}.zip`, + ]); for (const gameId of [ "pink-panther-hokus-pokus", "pink-panther-passport-to-peril", @@ -386,6 +434,8 @@ describe("build metadata", () => { const metadata = JSON.parse(await readFile(paths.versionJson, "utf8")); expect(metadata.offlineBytes).toBeGreaterThan(0); expect(metadata.bundledGameBytes).toBeGreaterThan(0); + expect(Number.isFinite(Date.parse(metadata.releasedAt))).toBeTrue(); + expect(metadata.stabilityDelayMs).toBe(6 * 60 * 60 * 1000); expect(manifest.version).toBe("26.07.28-abcdef1"); expect(manifest.games["bike-mania"].type).toBe("swf"); expect(manifest.games.doom.type).toBe("iframe"); @@ -596,6 +646,8 @@ describe("atomic build", () => { sourceDir: source, outputDir: output, version: "26.07.28-abcdef1", + releasedAt: "2026-07-28T12:00:00.000Z", + stabilityDelayMs: 2 * 60 * 60 * 1000, installRuffle: async (jsDir) => writeFiles(jsDir, { "ruffle.js": "ruffle", @@ -642,6 +694,8 @@ describe("atomic build", () => { expect(metadata.bundledGameBytes).toBeGreaterThan(0); expect(metadata.revision).toBe("abcdef1"); expect(metadata.version).toBe("26.07.28-abcdef1"); + expect(metadata.releasedAt).toBe("2026-07-28T12:00:00.000Z"); + expect(metadata.stabilityDelayMs).toBe(2 * 60 * 60 * 1000); await writeFile( scummvmIndexPath, diff --git a/tests/offline.test.ts b/tests/offline.test.ts index 857c1c60..1be06880 100644 --- a/tests/offline.test.ts +++ b/tests/offline.test.ts @@ -164,6 +164,8 @@ test("offline updates", async () => { }, }; const testIntegrity = `sha384-${"A".repeat(64)}`; + const now = 1_800_000_000_000; + const sixHours = 6 * 60 * 60 * 1000; for (const entry of [ manifest.runtime, ...Object.values(manifest.runtimes), @@ -178,9 +180,17 @@ test("offline updates", async () => { active: new Worker("activated", manifest.version), }), remoteVersion = "26.07.29-aaaaaaa", + remoteReleasedAt = new Date(now - sixHours).toISOString(), + stabilityDelayMs = sixHours, storageValues = {}, sessionValues = {}, } = {}) => { + let remote = { + releasedAt: remoteReleasedAt, + stabilityDelayMs, + version: remoteVersion, + }; + let currentTime = now; const serviceWorker = new Events(); serviceWorker.controller = registration.active; serviceWorker.registerCalls = []; @@ -220,7 +230,7 @@ test("offline updates", async () => { reloads += 1; }, }, - Date: { now: () => 1_800_000_000_000 }, + Date: { now: () => currentTime }, clearTimeout() {}, setTimeout: (callback, delay) => { timers.push({ callback, delay }); @@ -246,8 +256,10 @@ test("offline updates", async () => { if (String(url).startsWith("/version.json")) { return new Response( JSON.stringify({ - version: remoteVersion, - revision: remoteVersion.split("-").at(-1), + version: remote.version, + revision: remote.version.split("-").at(-1), + releasedAt: remote.releasedAt, + stabilityDelayMs: remote.stabilityDelayMs, offlineBytes: 8_000_000, bundledGameBytes: 10, }), @@ -266,6 +278,12 @@ test("offline updates", async () => { fetches, getReloads: () => reloads, registration, + setNow: (value) => { + currentTime = value; + }, + setRemote: (value) => { + remote = { ...remote, ...value }; + }, serviceWorker, storage, sessionStorage, @@ -563,6 +581,45 @@ test("offline updates", async () => { /^\/sw\.js\?v=26\.07\.30-bbbbbbb&retry=1800000000000$/, ); assert.strictEqual(staleWaitingManager.getSnapshot().updateReady, true); + assert.deepStrictEqual(staleWaitingRegistration.waiting.messages, [ + { type: "SKIP_WAITING" }, + ]); + + const firstReleaseTime = now - 3 * 60 * 60 * 1000; + const delayedRegistration = new Registration({ + active: new Worker("activated", manifest.version), + waiting: new Worker("installed", "26.07.30-first111"), + }); + const delayed = makeEnvironment({ + registration: delayedRegistration, + remoteVersion: "26.07.30-first111", + remoteReleasedAt: new Date(firstReleaseTime).toISOString(), + }); + const delayedManager = createManager({ + currentVersion: manifest.version, + environment: delayed.environment, + }); + await delayedManager.initialize(); + assert.strictEqual(delayedManager.getSnapshot().phase, "update-pending"); + assert.deepStrictEqual(delayedRegistration.waiting.messages, []); + + const secondReleaseTime = now; + delayed.setRemote({ + version: "26.07.30-second22", + releasedAt: new Date(secondReleaseTime).toISOString(), + }); + delayed.setNow(secondReleaseTime + sixHours); + delayedRegistration.waiting = new Worker("installed", "26.07.30-second22"); + const nextLoadManager = createManager({ + currentVersion: manifest.version, + environment: delayed.environment, + }); + await nextLoadManager.initialize(); + assert.deepStrictEqual(delayedRegistration.waiting.messages, [ + { type: "SKIP_WAITING" }, + ]); + delayed.serviceWorker.dispatch("controllerchange"); + assert.strictEqual(delayed.getReloads(), 1); const activatedUpdate = makeEnvironment({ registration: new Registration({ diff --git a/tools/build-boxedwine-xp-filesystem.ts b/tools/build-boxedwine-xp-filesystem.ts new file mode 100644 index 00000000..27624ce9 --- /dev/null +++ b/tools/build-boxedwine-xp-filesystem.ts @@ -0,0 +1,117 @@ +import { createHash } from "node:crypto"; +import { readFile, writeFile } from "node:fs/promises"; +import { dirname, join, posix, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { unzipSync, zipSync, type Zippable } from "fflate"; + +const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const runtimeRoot = join(projectRoot, "site", "vendor", "boxedwine", "26R1"); +const fixedTime = new Date("2000-01-01T00:00:00Z"); +const sharedTrees = [ + "bin/", + "etc/", + "home/username/.wine/", + "lib/", + "opt/wine/bin/", + "opt/wine/lib/", + "opt/wine/share/", + "usr/", + "var/", +]; + +export const buildBoxedWineXpFilesystem = async ({ + sourcePath = join(runtimeRoot, "boxedwine.zip"), + tracePath = join(runtimeRoot, "xp-accessories-files.json"), + outputPath = join(runtimeRoot, "xp-accessories.zip"), +} = {}) => { + const source = new Uint8Array(await readFile(sourcePath)); + const sourceFiles = unzipSync(source); + const tracedPaths = JSON.parse(await readFile(tracePath, "utf8")) as string[]; + const selected = new Set(); + const missing: string[] = []; + + const addPath = (absolutePath: string, reportMissing = true): void => { + const path = absolutePath.replace(/^\/+/, ""); + if (!path) return; + const sourceName = sourceFiles[path] + ? path + : sourceFiles[`${path}/`] + ? `${path}/` + : `${path}.link`; + if (!sourceFiles[sourceName]) { + if (reportMissing) missing.push(absolutePath); + return; + } + if (selected.has(sourceName)) return; + selected.add(sourceName); + const segments = sourceName.split("/"); + for (let index = 1; index < segments.length; index += 1) { + const directory = `${segments.slice(0, index).join("/")}/`; + if (sourceFiles[directory]) selected.add(directory); + } + if (sourceName.endsWith(".link")) { + const target = new TextDecoder().decode(sourceFiles[sourceName]); + const resolvedTarget = target.startsWith("/") + ? target + : posix.resolve(posix.dirname(`/${path}`), target); + addPath(resolvedTarget, false); + } + }; + + for (const absolutePath of tracedPaths) { + addPath(absolutePath); + } + for (const name of Object.keys(sourceFiles)) { + if ( + !name.includes("/") || + sharedTrees.some((prefix) => name.startsWith(prefix)) + ) { + addPath(`/${name}`, false); + } + } + if (missing.length > 0) { + throw new Error(`Missing traced BoxedWine files:\n${missing.join("\n")}`); + } + + const outputFiles: Zippable = {}; + for (const name of [...selected].sort()) { + const content = sourceFiles[name]; + const isDirectory = name.endsWith("/"); + const isExecutable = + isDirectory || + (content[0] === 0x7f && + content[1] === 0x45 && + content[2] === 0x4c && + content[3] === 0x46); + outputFiles[name] = [ + content, + { + attrs: + ((isDirectory ? 0o040000 : 0o100000) | + (isExecutable ? 0o755 : 0o644)) << + 16, + mtime: fixedTime, + os: 3, + }, + ]; + } + const output = zipSync(outputFiles, { level: 9, mtime: fixedTime }); + await writeFile(outputPath, output); + + const sha256 = (content: Uint8Array) => + createHash("sha256").update(content).digest("hex"); + return { + sourceBytes: source.byteLength, + sourceSha256: sha256(source), + tracedPaths: tracedPaths.length, + includedEntries: selected.size, + missing, + outputBytes: output.byteLength, + outputSha256: sha256(output), + }; +}; + +if (import.meta.main) { + console.log(JSON.stringify(await buildBoxedWineXpFilesystem(), null, 2)); +} diff --git a/tools/deploy.ts b/tools/deploy.ts index 85567833..d991378c 100644 --- a/tools/deploy.ts +++ b/tools/deploy.ts @@ -33,6 +33,7 @@ export const FFLATE_JS_PATH = "vendor/fflate/index.js"; export const JS_DOS_ROOT_PATH = "vendor/js-dos"; export const WEBTORRENT_JS_PATH = "vendor/webtorrent/webtorrent.min.js"; export const RELEASES_PATH = "releases"; +export const DEFAULT_UPDATE_STABILITY_DELAY_MS = 6 * 60 * 60 * 1000; const NODE_MODULES = join(PROJECT_DIR, "node_modules"); const FFLATE_SOURCE = join(NODE_MODULES, "fflate"); const JS_DOS_SOURCE = join(NODE_MODULES, "js-dos"); @@ -568,6 +569,8 @@ export async function updateHtml( const javaScriptPath = join(boxedWineRoot, "boxedwine.js"); const shellPath = join(boxedWineRoot, "boxedwine-shell.js"); const indexPath = join(boxedWineRoot, "index.html"); + const rootZipPath = join(boxedWineRoot, "xp-accessories.zip"); + const rootZipName = `xp-accessories-${await getShortHash(rootZipPath)}.zip`; const wasmName = `boxedwine.${await getShortHash(wasmPath)}.wasm`; let javaScript = await readFile(javaScriptPath, "utf8"); javaScript = await replaceExactlyOnce( @@ -594,8 +597,34 @@ export async function updateHtml( ); await writeFile(indexPath, boxedWineIndex); const indexName = `index.${await getShortHash(indexPath)}.html`; + const preloadPath = join(boxedWineRoot, "preload.json"); + if (await isFile(preloadPath)) { + await writeFile( + preloadPath, + `${JSON.stringify( + { + files: [ + "boxedwine-startup.js", + shellName, + javaScriptName, + wasmName, + indexName, + rootZipName, + ], + }, + null, + 2, + )}\n`, + ); + } const integrationPath = join(paths.root, "apps", "core", "boxedwine.js"); let integration = await readFile(integrationPath, "utf8"); + integration = await replaceExactlyOnce( + integration, + /const ROOT_ARCHIVE = "xp-accessories";/g, + `const ROOT_ARCHIVE = "${rootZipName.slice(0, -4)}";`, + "Could not version the BoxedWine root filesystem reference", + ); integration = await replaceExactlyOnce( integration, /`\$\{RUNTIME_ROOT\}index\.html`/g, @@ -608,6 +637,8 @@ export async function updateHtml( rename(wasmPath, join(boxedWineRoot, wasmName)), rename(javaScriptPath, join(boxedWineRoot, javaScriptName)), rename(shellPath, join(boxedWineRoot, shellName)), + rename(rootZipPath, join(boxedWineRoot, rootZipName)), + rm(join(boxedWineRoot, "boxedwine.zip")), ]); } console.log(` - Set deployment version to ${version}`); @@ -844,7 +875,17 @@ export async function versionOfflineGameManifest( export async function writeVersionMetadata( paths: BuildPaths, version: string, + { + releasedAt = new Date().toISOString(), + stabilityDelayMs = DEFAULT_UPDATE_STABILITY_DELAY_MS, + }: { releasedAt?: string; stabilityDelayMs?: number } = {}, ): Promise { + if (!Number.isFinite(Date.parse(releasedAt))) { + throw new Error("Release time must be a valid date"); + } + if (!Number.isInteger(stabilityDelayMs) || stabilityDelayMs < 0) { + throw new Error("Update stability delay must be a non-negative integer"); + } const files = await walkFiles(paths.root); let offlineBytes = 0; for (const path of files) { @@ -877,7 +918,9 @@ export async function writeVersionMetadata( `${JSON.stringify({ bundledGameBytes, offlineBytes, + releasedAt, revision, + stabilityDelayMs, version, })}\n`, ); @@ -1117,7 +1160,7 @@ export async function validateOutput(outputDir: string): Promise { } if ( Object.keys(versionMetadata).sort().join(",") !== - "bundledGameBytes,offlineBytes,revision,version" + "bundledGameBytes,offlineBytes,releasedAt,revision,stabilityDelayMs,version" ) { throw new Error("Build output has invalid version metadata"); } @@ -1125,7 +1168,11 @@ export async function validateOutput(outputDir: string): Promise { !Number.isInteger(versionMetadata.offlineBytes) || (versionMetadata.offlineBytes as number) <= 0 || !Number.isInteger(versionMetadata.bundledGameBytes) || - (versionMetadata.bundledGameBytes as number) <= 0 + (versionMetadata.bundledGameBytes as number) <= 0 || + typeof versionMetadata.releasedAt !== "string" || + !Number.isFinite(Date.parse(versionMetadata.releasedAt)) || + !Number.isInteger(versionMetadata.stabilityDelayMs) || + (versionMetadata.stabilityDelayMs as number) < 0 ) { throw new Error("Build output has invalid offline download size"); } @@ -1387,8 +1434,10 @@ export async function validatePrecacheIntegrity( export interface BuildOptions { outputDir?: string; + releasedAt?: string; revision?: string; sourceDir?: string; + stabilityDelayMs?: number; version?: string; installRuffle?: (jsDir: string) => Promise; generate?: (outputDir: string, version?: string) => Promise; @@ -1396,8 +1445,10 @@ export interface BuildOptions { export async function build({ outputDir = DEFAULT_OUTPUT_DIR, + releasedAt, revision = "HEAD", sourceDir = SOURCE_DIR, + stabilityDelayMs = DEFAULT_UPDATE_STABILITY_DELAY_MS, version, installRuffle: installRuffleRuntime = installRuffle, generate = (directory, buildVersion) => @@ -1434,7 +1485,10 @@ export async function build({ await installRuffleRuntime(paths.js); await updateHtml(paths, deploymentVersion); await writeOfflineGameManifest(paths, deploymentVersion); - await writeVersionMetadata(paths, deploymentVersion); + await writeVersionMetadata(paths, deploymentVersion, { + releasedAt, + stabilityDelayMs, + }); await versionOfflineGameManifest(paths); await validateOutput(stagingDir); await assembleReleaseOutput(stagingDir, deploymentVersion); @@ -1450,23 +1504,36 @@ export async function build({ function parseArguments(arguments_: string[]): { outputDir: string; revision: string; + stabilityDelayMs: number; } { let outputDir = DEFAULT_OUTPUT_DIR; let revision = "HEAD"; + let stabilityDelayMs = DEFAULT_UPDATE_STABILITY_DELAY_MS; for (let index = 0; index < arguments_.length; index += 1) { const argument = arguments_[index]; - if (argument === "--output" || argument === "--revision") { + if ( + argument === "--output" || + argument === "--revision" || + argument === "--update-delay-hours" + ) { const value = arguments_[index + 1]; if (!value) throw new Error(`${argument} requires a value`); if (argument === "--output") outputDir = isAbsolute(value) ? value : resolve(value); - else revision = value; + else if (argument === "--revision") revision = value; + else { + const hours = Number(value); + if (!Number.isFinite(hours) || hours < 0) { + throw new Error("--update-delay-hours must be zero or greater"); + } + stabilityDelayMs = Math.round(hours * 60 * 60 * 1000); + } index += 1; } else { throw new Error(`Unknown argument: ${argument}`); } } - return { outputDir, revision }; + return { outputDir, revision, stabilityDelayMs }; } if (import.meta.main) {