From 30d404162494b5955f974753e3019f7dce25b2cf Mon Sep 17 00:00:00 2001 From: Hubert Date: Fri, 4 Sep 2026 14:25:45 +0800 Subject: [PATCH 1/4] Add browser-side SAP request signing (X-Apple-ActionSignature) Apple's auth endpoint now requires X-Apple-ActionSignature on every request. The signer runs Apple's obfuscated CommerceKit/CoreFP entry points under Unicorn emulation entirely in the browser, preserving the zero-trust invariant: the signer sees only the deviceIdentifier and public Apple assets, while the password stays client-side as opaque body bytes it signs in place. Engine: Unicorn 2.x only ships JIT TCG backends, which cannot execute under WebAssembly. This restores the QEMU 5.0.1 TCI interpreter into the tree (patched build chain in frontend/scripts/unicorn-wasm-patch), forces 64-bit virtual TCI registers on wasm32 (the 32-bit path is incomplete upstream), generates uniform-signature helper trampolines, adapts glib-compat comparators, and replaces the timeout thread with a deadline checked inside the interpreter loop. Browser: Mach-O 64 loader with dyld_info rebase/bind/weak/lazy fixups, libc/CF/IOKit guest shims, and the SAP machine orchestration running in a Web Worker. Verified against the native ipatool runtime bit-for- bit: identical Initialize context (0x400000000200) and identical Sign gating error (-42085 without the Apple key exchange). Backend: extracts the four public Apple binaries from OSXUpd10.9.pkg (xar range download -> bzip2 -> cpio, digest-pinned), strips fat binaries to the x86_64 slice (37.7 MB -> 22.5 MB, ~14 MB gzipped on the wire), and serves them via /api/sap-assets. Docker: release images prebake the stripped assets (fresh VPS serves them with zero network use); build stages run on BUILDPLATFORM with per-platform runtime dependency install. linux/386 is not published by official node images and lacks napi binaries - amd64/arm64 only. Auth flow: bag advertises sign-sap-setup* keys -> assets fetched and cached (Cache API) -> signer created per authenticate() call -> each attempt's exact body bytes signed and attached. Bags without the SAP keys gracefully degrade to the legacy flow. --- .github/workflows/docker.yml | 4 +- .gitignore | 9 + AGENTS.md | 142 +- Dockerfile | 31 +- backend/package-lock.json | 82 + backend/package.json | 2 + backend/scripts/extract-sap-assets.mts | 42 + backend/src/index.ts | 2 + backend/src/routes/sapAssets.ts | 74 + backend/src/services/sapAssets.ts | 641 ++++ frontend/scripts/unicorn-wasm-patch/build.sh | 61 + frontend/scripts/unicorn-wasm-patch/glue.c | 181 + .../patches/unicorn-2.1.4-tci-wasm.patch | 3085 +++++++++++++++++ frontend/src/apple/authenticate.ts | 33 +- frontend/src/apple/bag.ts | 22 +- frontend/src/apple/sap/assets.ts | 161 + frontend/src/apple/sap/client.ts | 163 + frontend/src/apple/sap/engine.ts | 289 ++ frontend/src/apple/sap/machImage.ts | 818 +++++ frontend/src/apple/sap/machine.ts | 416 +++ frontend/src/apple/sap/protocol.ts | 62 + frontend/src/apple/sap/shims.ts | 712 ++++ frontend/src/apple/sap/signer.ts | 118 + frontend/src/apple/sap/types.ts | 42 + frontend/src/apple/sap/vendor/unicorn.d.mts | 5 + frontend/src/apple/sap/vendor/unicorn.mjs | 16 + frontend/src/apple/sap/vendor/unicorn.wasm | Bin 0 -> 730937 bytes frontend/src/apple/sap/worker.ts | 143 + frontend/tests/sap/machImage.test.ts | 233 ++ frontend/tests/sap/machine-live.mjs | 62 + frontend/vite.config.ts | 5 + 31 files changed, 7603 insertions(+), 53 deletions(-) create mode 100644 backend/scripts/extract-sap-assets.mts create mode 100644 backend/src/routes/sapAssets.ts create mode 100644 backend/src/services/sapAssets.ts create mode 100755 frontend/scripts/unicorn-wasm-patch/build.sh create mode 100644 frontend/scripts/unicorn-wasm-patch/glue.c create mode 100644 frontend/scripts/unicorn-wasm-patch/patches/unicorn-2.1.4-tci-wasm.patch create mode 100644 frontend/src/apple/sap/assets.ts create mode 100644 frontend/src/apple/sap/client.ts create mode 100644 frontend/src/apple/sap/engine.ts create mode 100644 frontend/src/apple/sap/machImage.ts create mode 100644 frontend/src/apple/sap/machine.ts create mode 100644 frontend/src/apple/sap/protocol.ts create mode 100644 frontend/src/apple/sap/shims.ts create mode 100644 frontend/src/apple/sap/signer.ts create mode 100644 frontend/src/apple/sap/types.ts create mode 100644 frontend/src/apple/sap/vendor/unicorn.d.mts create mode 100644 frontend/src/apple/sap/vendor/unicorn.mjs create mode 100755 frontend/src/apple/sap/vendor/unicorn.wasm create mode 100644 frontend/src/apple/sap/worker.ts create mode 100644 frontend/tests/sap/machImage.test.ts create mode 100644 frontend/tests/sap/machine-live.mjs diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index af8f3f24..8291d8f8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -48,7 +48,9 @@ jobs: uses: docker/build-push-action@v6 with: context: . - platforms: linux/amd64,linux/arm64 + # linux/386 is not published by the official node images and the napi +# binaries (@node-rs/crc32 via yauzl-promise) have no linux-ia32 build. +platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 42616533..dce82f62 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,12 @@ references/ mnt/ tunnel/ deploy.sh + +# SAP engine build chain (frontend/scripts/unicorn-wasm-patch/build.sh outputs) +frontend/scripts/unicorn-wasm-patch/unicorn-src/ +frontend/scripts/unicorn-wasm-patch/build/ +frontend/scripts/unicorn-wasm-patch/dist/ +frontend/src/apple/sap/vendor/unicorn-dbg.* + +# Local agent workspace +.video_agent/ diff --git a/AGENTS.md b/AGENTS.md index 276ab20f..59517e82 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,11 +9,12 @@ ## Project Structure -- `backend/` — Node.js/Express server (TypeScript, ESM) -- `frontend/` — React SPA (TypeScript, Vite, Tailwind CSS) -- `e2e/` — Playwright E2E tests (pnpm) -- `references/ApplePackage/` — Swift reference implementation (source of truth) -- Multi-stage Docker build (single container serves both) +- `backend/` — Node.js/Express server (TypeScript, ESM); tests in `backend/tests/` +- `frontend/` — React SPA (TypeScript, Vite, Tailwind CSS); tests in `frontend/tests/` (not collocated with src) +- `cloudflare/` + `wrangler.jsonc` — Cloudflare Workers + Containers deployment wrapper around the Docker image +- `Dockerfile` / `compose.yml` — single container serves both backend and SPA +- `frontend/scripts/unicorn-wasm-patch/` — patches + glue + build script for the Unicorn TCI WASM engine (see SAP section) +- `references/` is gitignored personal infrastructure (never commit); a local ApplePackage checkout may or may not exist ## Architecture — Zero-Trust @@ -57,17 +58,76 @@ The server is a blind TCP proxy. It NEVER sees Apple credentials. **Key invariant**: The server NEVER sees Apple credentials. All Apple TLS terminates at the browser via libcurl.js WASM (Mbed TLS 1.3). The server only receives public CDN URLs and non-secret metadata for IPA compilation. The bag proxy (`/api/bag`) only returns public Apple service URLs — no credentials pass through it. -## Reference Implementation +## Architecture — SAP Request Signing (X-Apple-ActionSignature) + +Apple requires every request to the auth endpoint to carry `X-Apple-ActionSignature: base64(Sign(bodyBytes))`. The signature is produced by obfuscated SAP entry points inside Apple's CommerceKit/CoreFP binaries (the same mechanism ipatool uses). Key property: **the signer's inputs are only the hardware ID (the per-account `deviceIdentifier`) plus public Apple assets — never credentials — but the signature covers the request body, which contains the password.** Therefore signing MUST stay in the browser; the zero-trust invariant is preserved. + +``` +┌─ Browser ────────────────────────────────────────────────────────┐ +│ 1. Bag (via /api/bag) advertises: │ +│ sign-sap-setup → setup exchange endpoint (POST plist) │ +│ sign-sap-setup-cert → certificate endpoint (GET plist) │ +│ sign-sap-version → protocol version (200) │ +│ 2. GET /api/sap-assets/:name → four Apple binaries (backend- │ +│ extracted, digest-pinned, browser-cached in the Cache API) │ +│ 3. SAP signer worker (Web Worker, off the UI thread): │ +│ Unicorn 2.1.4 → TCI interpreter backend → wasm │ +│ Mach-O x86_64 images loaded + dyld-info relocated in TS │ +│ entry points: initialize / exchange / sign / teardown │ +│ 4. Key exchange over the wisp tunnel (main thread): │ +│ GET cert → exchange(state 1) → POST setup → exchange(state 0)│ +│ 5. authenticate() signs each attempt's exact UTF-8 body bytes │ +│ and attaches X-Apple-ActionSignature │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### Frontend modules (`frontend/src/apple/sap/`) + +- `engine.ts` — Unicorn WASM wrapper; all guest addresses cross the JS boundary as doubles (exact below 2^53; the guest map stays below 2^48) +- `machImage.ts` — Mach-O 64 parser: fat-binary slicing, LC_SEGMENT_64, symtab lookup, dyld_info rebase/bind/weak/lazy opcodes. **Opcode tables**: rebase uses nibbles 0x00–0x80 (SET_TYPE 0x10, SET_SEGMENT 0x20, …); bind is shifted down one slot (DO_BIND 0x90) because rebase-only ADD_ADDR_IMM_SCALED occupies bind's 0x90 slot elsewhere. Segment offsets move in uint64 BigInt space — bind ADD_ADDR_ULEB deltas are 64-bit encodings of negative steps. Fixups targeting a segment's BSS tail (within vmsize, past fileSize) are skipped: dyld would zero-fill them. +- `shims.ts` — guest libc/CF/IOKit shims; 64-bit −1 constants are passed as `-1` (wasm's saturating f64→i64 conversion yields 0xFFFF…F, which a JS number cannot represent exactly) +- `machine.ts` — entry-point invocation, scratch/stack layout, output disposal +- `signer.ts` / `client.ts` / `worker.ts` — orchestration; emulation runs in a Web Worker, Apple network calls ride the wisp tunnel on the main thread +- `protocol.ts` — certificate fetch + setup exchange (plist `` round-trip via appleRequest) +- `assets.ts` — asset download with progress, SHA-256 verification (stripped-file pins), Cache API persistence; accepts both thin and fat Mach-O payloads +- `vendor/unicorn.mjs|.wasm` — prebuilt engine (regenerate via `frontend/scripts/unicorn-wasm-patch/build.sh`) + +### Unicorn TCI WASM build chain (`frontend/scripts/unicorn-wasm-patch/`) + +Unicorn 2.x only ships JIT TCG backends, which cannot execute under WebAssembly (no RWX, no wasm codegen). The patch (`patches/unicorn-2.1.4-tci-wasm.patch` against unicorn 2.1.4, whose QEMU base is 5.0.1): + +1. Restores the QEMU 5.0.1 TCI interpreter (unicorn stripped it) into the tree +2. Forces 64-bit virtual TCI registers on wasm32 — QEMU 5.0's 32-bit TCI path is riddled with TODO() stubs; the register file is virtual state, so 64-bit registers need no 64-bit host pointers +3. Generates uniform-signature helper trampolines (`qemu/target/i386/tci-wasm-tramp.c`): TCI invokes every helper through one cast signature, which wasm's strict indirect-call checks reject +4. Adapts glib-compat GTree comparators (2-arg vs 3-arg) and disables inline hook callbacks for the same reason +5. Replaces the timeout thread (no pthreads in wasm) with a wall-clock deadline checked inside the TCI interpreter loop; mprotect/mmap-based guest RAM becomes aligned malloc + +Build: `bash frontend/scripts/unicorn-wasm-patch/build.sh` (requires docker; emscripten runs in a container). The signed-off artifacts land in `frontend/src/apple/sap/vendor/`. + +### Backend asset pipeline + +`backend/src/services/sapAssets.ts` extracts the four binaries once from Apple's public OSXUpd10.9.pkg: xar TOC parse → HTTP range download of the Payload tail (~380 MB) → bzip2 stream (with a synthetic `BZh9` header from a fixed offset) → cpio (odc and newc formats) → pinned SHA-256 verification → **fat-binary stripping to the x86_64 slice** (the emulated guest architecture; CoreFP ships as an i386+x86_64 universal, so this halves it) → cache under `DATA_DIR/sap-assets`. All data is public Apple content (same trust class as the bag proxy). Specs carry two pin sets: the original Apple digest verifies the extraction; the stripped digest (a deterministic function of the original) verifies what is served and what the browser downloads. Distribution sizes: 37.7 MB original → 22.5 MB stripped → ~14 MB on the wire with the route's gzip response. The bz2 stream is truncated mid-file, so the decoder can emit a late crc error after the wanted members are captured — the pipeline swallows it by design (an unhandled rejection would crash Node). + +On startup `ensureSapAssets` prefers `DATA_DIR/sap-assets`, then seeds from the image-prebaked directory (`BUNDLED_SAP_ASSETS`, default `/opt/asspp/sap-assets`), and only then falls back to network extraction (the bzip2 decoder is imported lazily because cross-built images may lack a matching napi binary for the runtime arch — see Dockerfile). Routes (`backend/src/routes/sapAssets.ts`): `GET /api/sap-assets/status`, `POST /api/sap-assets/prepare`, `GET /api/sap-assets/:name` (gzip when accepted). -The Swift reference at `references/ApplePackage/` is the source of truth for Apple protocol behavior: +### Container image -- Field mappings (iTunes API → Software type) use Swift `CodingKeys` -- Authentication flow, bag endpoint, pod routing, error codes -- Always consult the reference when making protocol changes +The Dockerfile prebakes the stripped SAP assets at build time (a `sap-assets` stage runs `backend/scripts/extract-sap-assets.mts`; Docker layer caching makes it a no-op on rebuilds). Release images ship the assets, so a fresh VPS serves them with zero network use. Build stages run on `$BUILDPLATFORM` (JS artifacts are platform-independent); the runtime image installs production deps per target platform because `yauzl-promise` → `@node-rs/crc32` ships prebuilt napi binaries per arch. Published platforms: `linux/amd64`, `linux/arm64`. **linux/386 is out**: official node images dropped it and `@node-rs/crc32` has no linux-ia32 build. + +### SAP invariants + +- The signer only ever sees the deviceIdentifier and public Apple assets; the password reaches the signer solely as opaque body bytes it signs in-place — it is never transmitted anywhere except through the wisp tunnel inside the auth request itself +- Bag missing the SAP keys → signing is skipped (graceful degradation to the legacy flow) +- SAP session lifetime = one `authenticate()` call (created and closed inside; initialization ≈ 150–300 ms including the exchange round-trips) +- First login downloads ~14 MB over the wire (22.5 MB stripped assets, gzipped; progress is not yet surfaced in the UI — known limitation). Release images prebake the assets, so the backend serves them instantly + +## Reference Implementation + +The upstream Swift project ApplePackage is the source of truth for Apple protocol behavior (authentication flow, bag endpoint, pod routing, error codes). A local checkout may live at `references/ApplePackage/`, but `references/` is gitignored — it is **not part of this repository** and may be absent. When unavailable, the field mapping below and the existing `frontend/src/apple/*` implementation are the in-repo reference. ### iTunes API Field Mapping -The backend (`backend/src/routes/search.ts`) maps raw iTunes API fields to our `Software` type, matching the Swift CodingKeys in `references/ApplePackage/Sources/ApplePackage/Models/Software.swift`: +The backend (`backend/src/routes/search.ts`) maps raw iTunes API fields to our `Software` type, matching the Swift `CodingKeys` in ApplePackage's `Software.swift`: | iTunes Field | Software Field | | --------------------------- | -------------- | @@ -123,6 +183,7 @@ The backend proxies the bag endpoint via `GET /api/bag?guid=` using No - `tsx` for development, `tsc` for production build - SINF injector also handles optional `iTunesMetadata.plist` injection at IPA root - Bag proxy for `init.itunes.apple.com` +- SAP asset extraction service (xar + bzip2 + cpio) with digest pinning; routes under `/api/sap-assets` ### Backend Shared Utilities @@ -151,6 +212,8 @@ The backend proxies the bag endpoint via `GET /api/bag?guid=` using No - **AppIcon** — 3 sizes (40/56/80px), rounded corners, letter fallback - **Badge** — color-coded status pill - **ProgressBar** — gray track, blue fill, percentage label +- **ToastContainer** / `utils/toast.ts` — toast notifications (incl. account-context helpers) +- **GlobalDownloadNotifier** — global download status notifications - **icons** — shared SVG icon components (`HomeIcon`, `AccountsIcon`, `SearchIcon`, `DownloadsIcon`, `SettingsIcon`, `SunIcon`, `MoonIcon`, `SystemIcon`) used by Sidebar, MobileNav, and MobileHeader ### Frontend Shared Utilities (`utils/`) @@ -158,6 +221,8 @@ The backend proxies the bag endpoint via `GET /api/bag?guid=` using No - `utils/error.ts` — `getErrorMessage(e, fallback)` for standardized catch-block error extraction - `utils/crypto.ts` — AES-GCM encrypt/decrypt for account export/import - `utils/account.ts` — `accountHash()`, `accountStoreCountry()`, `firstAccountCountry()` +- `utils/toast.ts` — toast helpers (pairs with `ToastContainer`) +- `utils/version.ts` — numeric dot-separated version string comparison ### Import Ordering Convention @@ -211,59 +276,52 @@ The settings endpoint (`/api/settings`) must never reflect request headers (`x-f ## Testing -### Unit Tests +### Unit Tests (Vitest) ```bash -cd backend && npx vitest run # Node environment -cd frontend && npx vitest run # jsdom environment with fake-indexeddb +cd backend && npx vitest run # Node environment; tests in backend/tests/ +cd frontend && npx vitest run # jsdom environment with fake-indexeddb; tests in frontend/tests/ ``` -### E2E Tests (Playwright) +Frontend tests mirror the src layout under `frontend/tests/` (`apple/`, `api/`, `store/`, `utils/`) — add new tests there, not next to source files. -```bash -cd e2e && pnpm test # Local (requires Docker on port 8080) -docker compose --profile test run --rm playwright # Docker-based -bash e2e/docker-test.sh # Full: build + test + zero-trust verify -``` +There is no E2E suite or lint script in the repo currently. Real-account Docker verification (2026-02-22): authentication succeeds through Wisp, and backend logs contain only connection/stream metadata (no Apple credentials, password tokens, or cookies). -E2E tests import from `./fixtures` instead of `@playwright/test`. +SAP-specific tests: -WebSocket proxy tests use `location.host` to derive URLs dynamically, so they work both locally (`localhost:8080`) and in Docker (`asspp:8080`). +- `frontend/tests/sap/machImage.test.ts` (vitest) — synthetic Mach-O builder exercising symbol export, rebase/bind opcodes, addends, and BSS-tail fixup tolerance +- `frontend/tests/sap/machine-live.mjs` (manual, `npx tsx`) — full chain against the real Apple assets served by the backend: machine open → Initialize (context matches the native ipatool runtime bit-for-bit: `0x400000000200`) → Sign correctly gated by the key exchange (`-42085` without it, identical to native). Requires `SAP_ASSET_DIR` pointing at a flat copy of the four assets, or the nested extraction layout -Real-account Docker verification (2026-02-22): authentication succeeds through Wisp, and backend logs contain only connection/stream metadata (no Apple credentials, password tokens, or cookies). - -E2E tests cover: - -- Wisp proxy (accepts /wisp/ WebSocket, rejects non-wisp paths) -- Add account flow (device ID field, randomize button, auth) -- Account detail (device ID, pod display) -- Settings page (no global device ID section) -- Search/lookup by bundle ID (verifies iTunes field mapping) -- Downloads API (iTunesMetadata support, backward compatibility) - -### Test Account - -Test credentials are stored in environment variables (`TEST_EMAIL`, `TEST_PASSWORD`, `TEST_DEVICE_ID`, `TEST_BUNDLE_ID`) and must never be committed to the repository. +Test credentials, if ever needed, belong in environment variables (`TEST_EMAIL`, `TEST_PASSWORD`, `TEST_DEVICE_ID`, `TEST_BUNDLE_ID`) and must never be committed. ## Deployment +### Docker Compose (self-host) + ```bash -docker compose up --build -d # Builds and runs on port 8080 +docker compose up -d # Runs prebuilt image ghcr.io/lakr233/assppweb:latest on port 8080 ``` +`compose.yml` pulls the published image (no local build), mounts `./mnt/asspp-data:/data` for `DATA_DIR`, and supports `ACCESS_PASSWORD` / `DOWNLOAD_THREADS` env vars. The `Dockerfile` at the repo root is what CI builds and publishes that image. + Single container serves both the Express backend and the Vite-built React SPA. SPA routes are handled by serving `index.html` for all non-API paths. -### Docker E2E Testing +### Cloudflare Workers + Containers -The `compose.yml` includes a `playwright` service under the `test` profile: +`wrangler.jsonc` + `cloudflare/src/index.ts` deploy the same Docker image as a Cloudflare Container behind a Worker: ```bash -docker compose --profile test run --rm playwright +npx wrangler login +npx wrangler deploy ``` -This runs Playwright inside the official `mcr.microsoft.com/playwright` image, connecting to the app container via Docker internal DNS (`http://asspp:8080`). The `asspp` service has a healthcheck so the test container waits until the app is ready. +- Requires the Cloudflare Workers **Paid** plan (Containers are not on Free) +- All HTTP/WebSocket traffic routes to one named container instance (`main`) to keep state consistent; `max_instances: 1` +- Container filesystem is **ephemeral** — compiled IPAs are lost when the container stops/sleeps (`sleepAfter = "2h"`) +- Health ping endpoint: `/api/settings`; worker injects `x-forwarded-proto: https` when missing to avoid redirect loops +- `wrangler.jsonc` build command installs `@cloudflare/containers` on the fly, so deploys need no persistent devDependency -The `e2e/docker-test.sh` script automates the full flow: build, test, and verify zero-trust by scanning backend logs for credential leaks. +`README.md` documents the full deploy matrix (Cloudflare button, Railway with its Cloudflare-proxy TLS caveat, reverse-proxy WebSocket requirements for `/wisp/`). ## Interface Design System diff --git a/Dockerfile b/Dockerfile index bb5379f3..7446a989 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,18 @@ -# Stage 1: Build frontend -FROM node:20-alpine AS frontend-build +# Build stages run on the build machine's native architecture (BUILDPLATFORM) +# and produce platform-independent artifacts; only the runtime layer is built +# per target platform. This keeps linux/386 builds as cheap as amd64/arm64. +FROM --platform=$BUILDPLATFORM node:20-alpine AS frontend-build WORKDIR /app/frontend COPY frontend/package*.json ./ RUN npm ci COPY frontend/ ./ RUN npm run build -# Stage 2: Build backend -FROM node:20-alpine AS backend-build +# Produces dist/ (pure JS) plus a full node_modules used by the asset +# extraction stage below. This node_modules is NOT copied into the runtime +# image: native binaries (napi) must match the target platform, which is +# installed there per-arch instead. +FROM --platform=$BUILDPLATFORM node:20-alpine AS backend-build RUN apk add --no-cache python3 make g++ WORKDIR /app/backend COPY backend/package*.json ./ @@ -15,14 +20,26 @@ RUN npm ci COPY backend/ ./ RUN npm run build -# Stage 3: Runtime +# Downloads the public Apple update package once (~380 MB over range +# requests), verifies the pinned digests, strips fat binaries to their x86_64 +# slices, and emits ~22.5 MB of assets. Docker layer caching makes this a +# no-op on rebuilds unless the pinned digests change. +FROM --platform=$BUILDPLATFORM node:20-alpine AS sap-assets +WORKDIR /app/backend +COPY --from=backend-build /app/backend ./ +ARG SAP_ASSETS_OUT=/out +RUN DATA_DIR=/tmp/sap-extract-work node --import tsx scripts/extract-sap-assets.mts ${SAP_ASSETS_OUT} + FROM node:20-alpine RUN apk add --no-cache zip WORKDIR /app COPY --from=backend-build /app/backend/dist ./dist -COPY --from=backend-build /app/backend/node_modules ./node_modules -COPY --from=backend-build /app/backend/package.json ./ +COPY backend/package*.json ./ +# Native modules (e.g. @node-rs/crc32 via yauzl-promise) ship prebuilt napi +# binaries per platform; installing here picks the target arch automatically. +RUN npm ci --omit=dev && npm cache clean --force COPY --from=frontend-build /app/frontend/dist ./public +COPY --from=sap-assets /out /opt/asspp/sap-assets RUN mkdir -p /data/packages EXPOSE 8080 ARG BUILD_COMMIT=unknown diff --git a/backend/package-lock.json b/backend/package-lock.json index 2b1948b4..ae8a8210 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -14,6 +14,7 @@ "bplist-parser": "^0.3.2", "express": "^4.21.2", "plist": "^3.1.0", + "unbzip2-stream": "^1.4.3", "uuid": "^11.0.5", "yauzl-promise": "^4.0.0" }, @@ -26,6 +27,7 @@ "@types/node": "^22.13.1", "@types/plist": "^3.0.5", "@types/supertest": "^6.0.3", + "@types/unbzip2-stream": "^1.4.3", "@types/uuid": "^10.0.0", "@types/ws": "^8.18.1", "@types/yauzl-promise": "^4.0.1", @@ -1712,6 +1714,26 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-D8X5uuJRISqc8YtwL8jNW2FpPdUOCYXbfD6zNROCTbVXK9nawucxh10tVXE3MPjnHdRA1LvB0zDxVya/lBsnYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/through": "*" + } + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -2040,6 +2062,30 @@ "node": ">= 5.10.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/bufferutil": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", @@ -2936,6 +2982,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -3864,6 +3930,12 @@ "dev": true, "license": "MIT" }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -4017,6 +4089,16 @@ "node": ">=14.17" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undici": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.21.0.tgz", diff --git a/backend/package.json b/backend/package.json index 16861660..e3385dbf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -17,6 +17,7 @@ "bplist-parser": "^0.3.2", "express": "^4.21.2", "plist": "^3.1.0", + "unbzip2-stream": "^1.4.3", "uuid": "^11.0.5", "yauzl-promise": "^4.0.0" }, @@ -29,6 +30,7 @@ "@types/node": "^22.13.1", "@types/plist": "^3.0.5", "@types/supertest": "^6.0.3", + "@types/unbzip2-stream": "^1.4.3", "@types/uuid": "^10.0.0", "@types/ws": "^8.18.1", "@types/yauzl-promise": "^4.0.1", diff --git a/backend/scripts/extract-sap-assets.mts b/backend/scripts/extract-sap-assets.mts new file mode 100644 index 00000000..c20c7c10 --- /dev/null +++ b/backend/scripts/extract-sap-assets.mts @@ -0,0 +1,42 @@ +// Build-time SAP asset extraction entrypoint (used by the Dockerfile). +// Downloads the public Apple update package once, verifies the pinned +// digests, strips fat binaries to their x86_64 slices, and writes the four +// files into OUT_DIR so the runtime image can ship them prebaked. + +import { mkdir, copyFile } from "node:fs/promises"; +import path from "node:path"; + +const outDir = process.argv[2]; + +if (!outDir) { + console.error("usage: DATA_DIR= tsx scripts/extract-sap-assets.mts "); + process.exit(1); +} + +if (!process.env.DATA_DIR) { + console.error("DATA_DIR must be set (config.ts reads it at import time)"); + process.exit(1); +} + +// Imported after the DATA_DIR guard: the config module captures the env at +// import time, so a default assigned here would arrive too late. +const { ensureSapAssets, readCachedAsset, SAP_ASSET_SPECS } = await import( + "../src/services/sapAssets.ts" +); + +await ensureSapAssets(); +await mkdir(outDir, { recursive: true }); + +for (const spec of SAP_ASSET_SPECS) { + const data = await readCachedAsset(spec.name); + if (!data) { + throw new Error(`asset ${spec.name} missing after extraction`); + } + await copyFile( + path.join(process.env.DATA_DIR, "sap-assets", spec.name), + path.join(outDir, spec.name), + ); + console.log(`${spec.name}: ${data.length} bytes`); +} + +console.log(`SAP assets prebaked into ${outDir}`); diff --git a/backend/src/index.ts b/backend/src/index.ts index 7151d943..7ccd4e95 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -14,6 +14,7 @@ import packageRoutes from "./routes/packages.js"; import installRoutes from "./routes/install.js"; import settingsRoutes from "./routes/settings.js"; import bagRoutes from "./routes/bag.js"; +import sapAssetRoutes from "./routes/sapAssets.js"; const app = express(); @@ -30,6 +31,7 @@ app.use("/api", packageRoutes); app.use("/api", installRoutes); app.use("/api", settingsRoutes); app.use("/api", bagRoutes); +app.use("/api", sapAssetRoutes); // Serve static frontend files const publicDir = path.resolve(import.meta.dirname, "../public"); diff --git a/backend/src/routes/sapAssets.ts b/backend/src/routes/sapAssets.ts new file mode 100644 index 00000000..db936669 --- /dev/null +++ b/backend/src/routes/sapAssets.ts @@ -0,0 +1,74 @@ +// SAP asset routes: status/prepare for the extraction job and authenticated +// downloads of the four cached Apple binaries. The files are public Apple +// content (extracted from a public software update package, digest-pinned), +// placing them in the same trust class as the bag proxy. + +import { Router, Request, Response } from "express"; +import zlib from "node:zlib"; +import { + SAP_ASSET_SPECS, + ensureSapAssets, + readCachedAsset, + sapAssetsState, +} from "../services/sapAssets.js"; + +const router = Router(); + +router.get("/sap-assets/status", (_req: Request, res: Response) => { + res.json(sapAssetsState()); +}); + +router.post("/sap-assets/prepare", async (_req: Request, res: Response) => { + try { + // Fire-and-observe: the caller polls /status for progress. + const preparation = ensureSapAssets(); + void preparation.catch(() => undefined); + res.json(sapAssetsState()); + } catch (error) { + res.status(500).json({ + error: error instanceof Error ? error.message : String(error), + }); + } +}); + +router.get("/sap-assets/:name", async (req: Request, res: Response) => { + const name = req.params.name as string; + const spec = SAP_ASSET_SPECS.find((candidate) => candidate.name === name); + if (!spec) { + res.status(404).json({ error: "Unknown SAP asset" }); + return; + } + + let data = await readCachedAsset(name); + if (!data) { + try { + await ensureSapAssets(); + data = await readCachedAsset(name); + } catch (error) { + res.status(503).json({ + error: + error instanceof Error ? error.message : "SAP asset extraction failed", + }); + return; + } + } + if (!data) { + res.status(503).json({ error: "SAP asset unavailable" }); + return; + } + + res.setHeader("Content-Type", "application/octet-stream"); + res.setHeader("ETag", `"${spec.strippedSha256}"`); + res.setHeader("Cache-Control", "private, max-age=31536000, immutable"); + // gzip cuts the ~22.5 MiB bundle to ~14 MiB on the wire (CoreFP's obfuscated + // __TEXT compresses at ~50%, the icxs data blob at ~13% of its size). + if (req.headers["accept-encoding"]?.includes("gzip") && data.length > 65536) { + res.setHeader("Content-Encoding", "gzip"); + res.setHeader("Vary", "Accept-Encoding"); + res.send(zlib.gzipSync(data, { level: 9 })); + return; + } + res.send(data); +}); + +export default router; diff --git a/backend/src/services/sapAssets.ts b/backend/src/services/sapAssets.ts new file mode 100644 index 00000000..16f1b99e --- /dev/null +++ b/backend/src/services/sapAssets.ts @@ -0,0 +1,641 @@ +// SAP asset extraction service. +// +// Apple's SAP signer runs four binaries extracted from a public OS X 10.9 +// software update package on swcdn.apple.com. The backend downloads the +// package's xar Payload once via HTTP range requests, decompresses the bzip2 +// stream from a fixed offset, and pulls the four files out of the cpio +// archive — verifying each against its pinned SHA-256 digest before caching +// them under DATA_DIR/sap-assets. All data is public Apple content; no +// credentials are involved at any point. +// +// Wire format details mirror ipatool's internal/sap/assets (and the +// extraction pipeline verified against the pinned digests on 2026-09-02). + +import https from "node:https"; +import zlib from "node:zlib"; +import { createHash } from "node:crypto"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { Readable, Transform } from "node:stream"; +import { config } from "../config.js"; + +const UPDATE_URL = + "https://swcdn.apple.com/content/downloads/27/34/041-98128-A_SYPWICN3KH/5dqkl4rqgbsr18yzy61yeie9g3cmjc5hiv/OSXUpd10.9.pkg"; +const BZ2_OFFSET = 0x352f40d5; +const CPIO_SKIP = 0x3a4; +const XAR_HEADER_SIZE = 28; + +export interface SapAssetSpec { + name: string; + /** Path inside the cpio archive. */ + archivePath: string; + /** Original Apple file: size + digest verify the extraction itself. */ + size: number; + sha256: string; + /** + * Distributed file: fat binaries are stripped to their x86_64 slice (the + * emulated guest architecture) before caching, halving CoreFP. The + * stripped digest is a deterministic function of the original — the + * extraction still only accepts bytes matching the Apple digest above. + */ + strippedSize: number; + strippedSha256: string; +} + +export const SAP_ASSET_SPECS: SapAssetSpec[] = [ + { + name: "CommerceKit", + archivePath: + "./System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/CommerceKit", + size: 3271840, + sha256: "b84ff12c21987856c0a17b78f1ad82b73195a6dec5f3b208a17d245555a2c8a2", + // Thin x86_64 image; no fat wrapper to strip. + strippedSize: 3271840, + strippedSha256: + "b84ff12c21987856c0a17b78f1ad82b73195a6dec5f3b208a17d245555a2c8a2", + }, + { + name: "CommerceCore", + archivePath: + "./System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Frameworks/CommerceCore.framework/Versions/A/CommerceCore", + size: 207744, + sha256: "c5401e57402230f3c876409d295319ddf1e61287bc882683c5d61277be7bc1f2", + strippedSize: 115712, + strippedSha256: + "05707cd937798f2b5189471f513672ac6242ffbffc38f06ed2e4fb4345156819", + }, + { + name: "CoreFP", + archivePath: + "./System/Library/PrivateFrameworks/CoreFP.framework/Versions/A/CoreFP", + size: 29014912, + sha256: "f19141336be4198d0f8991bb00017c915efc7aeaece36c345f7faa1237ea6074", + strippedSize: 14904192, + strippedSha256: + "97c899f2fb076bdf7f810fe00ceb335d4af85efab0f2de737ad0aedd991c8277", + }, + { + name: "CoreFP.icxs", + archivePath: + "./System/Library/PrivateFrameworks/CoreFP.framework/Versions/A/CoreFP.icxs", + size: 5288352, + sha256: "473e78af86979f5bd4f6269561caf770b3d16c098d918846eeac8cdd2fe6566a", + // FairPlay data blob; no architecture slices. + strippedSize: 5288352, + strippedSha256: + "473e78af86979f5bd4f6269561caf770b3d16c098d918846eeac8cdd2fe6566a", + }, +]; + +export type SapAssetsState = + | { status: "idle" } + | { + status: "extracting"; + progress: number; + downloadedBytes: number; + totalBytes: number; + } + | { status: "ready" } + | { status: "error"; error: string }; + +let state: SapAssetsState = { status: "idle" }; +let extraction: Promise | null = null; + +export function sapAssetsState(): SapAssetsState { + return state; +} + +function cacheDir(): string { + return path.join(config.dataDir, "sap-assets"); +} + +function assetPath(name: string): string { + const spec = SAP_ASSET_SPECS.find((candidate) => candidate.name === name); + if (!spec) { + throw new Error("unknown SAP asset"); + } + return path.join(cacheDir(), spec.name); +} + +export async function readCachedAsset(name: string): Promise { + const spec = SAP_ASSET_SPECS.find((candidate) => candidate.name === name); + if (!spec) { + return null; + } + try { + const data = await fs.readFile(assetPath(name)); + return verifyStripped(spec, data) ? data : null; + } catch { + return null; + } +} + +function verifyStripped(spec: SapAssetSpec, data: Buffer): boolean { + if (data.length !== spec.strippedSize) { + return false; + } + return ( + createHash("sha256").update(data).digest("hex") === spec.strippedSha256 + ); +} + +/** + * Extracts the x86_64 slice from a fat (universal) Mach-O, returning the + * input untouched for thin images and non-Mach-O data. Mirrors the browser's + * amd64Slice in frontend/src/apple/sap/machImage.ts. + */ +function stripToX86_64(data: Buffer): Buffer { + if (data.length < 8) { + return data; + } + const magic = data.readUInt32BE(0); + if (magic !== 0xcafebabe && magic !== 0xcafebabf) { + return data; + } + const wide = magic === 0xcafebabf; + const count = data.readUInt32BE(4); + const entrySize = wide ? 32 : 20; + for (let index = 0; index < count; index++) { + const entry = 8 + index * entrySize; + const cputype = data.readInt32BE(entry); + if (cputype !== 0x01000007) { + continue; // x86_64 + } + const offset = wide + ? Number(data.readBigUInt64BE(entry + 8)) + : data.readUInt32BE(entry + 8); + const size = wide + ? Number(data.readBigUInt64BE(entry + 16)) + : data.readUInt32BE(entry + 12); + if (offset + size > data.length) { + throw new Error("x86_64 slice exceeds input size"); + } + return data.subarray(offset, offset + size); + } + return data; +} + +/** + * Directory of assets prebaked into the container image (see Dockerfile). + * When present, a fresh volume is seeded from it without any network use. + */ +export function bundledSapAssetsDir(): string { + return process.env.BUNDLED_SAP_ASSETS ?? "/opt/asspp/sap-assets"; +} + +/** Ensures assets are extracted and cached; concurrent callers share the job. */ +export async function ensureSapAssets(): Promise { + for (const spec of SAP_ASSET_SPECS) { + if (!(await readCachedAsset(spec.name))) { + if (await seedFromBundled()) { + break; + } + await extract(); + return; + } + } + state = { status: "ready" }; +} + +async function seedFromBundled(): Promise { + const bundled = bundledSapAssetsDir(); + let any = false; + try { + await fs.access(bundled); + } catch { + return false; + } + for (const spec of SAP_ASSET_SPECS) { + if (await readCachedAsset(spec.name)) { + continue; + } + try { + const data = await fs.readFile(path.join(bundled, spec.name)); + if (!verifyStripped(spec, data)) { + continue; + } + await fs.mkdir(cacheDir(), { recursive: true }); + const target = assetPath(spec.name); + await fs.writeFile(`${target}.tmp`, data); + await fs.rename(`${target}.tmp`, target); + any = true; + } catch { + // bundled file missing/corrupt; fall through to extraction + } + } + return any; +} + +async function extract(): Promise { + if (extraction) { + return extraction; + } + extraction = runExtraction().finally(() => { + extraction = null; + }); + return extraction; +} + +async function runExtraction(): Promise { + state = { status: "extracting", progress: 0, downloadedBytes: 0, totalBytes: 0 }; + + try { + const location = await locatePayload(); + const streamStart = location.heapOffset + BZ2_OFFSET; + const totalBytes = location.length - BZ2_OFFSET; + state = { status: "extracting", progress: 0, downloadedBytes: 0, totalBytes }; + + await fs.mkdir(cacheDir(), { recursive: true }); + + const results = new Map(); + const wanted = new Map void>(); + for (const spec of SAP_ASSET_SPECS) { + wanted.set(spec.archivePath, (data) => results.set(spec.name, data)); + } + + let allFoundResolve: (() => void) | null = null; + const allFound = new Promise((resolve) => { + allFoundResolve = resolve; + }); + const skipper = new SkipStream(CPIO_SKIP); + const extractor = new CpioExtractor(wanted, () => allFoundResolve?.()); + + let downloadedBytes = 0; + const cdnStream = rangeStream(streamStart, streamStart + totalBytes, (bytes) => { + downloadedBytes += bytes; + if (state.status === "extracting") { + state.downloadedBytes = downloadedBytes; + state.progress = Math.min(0.95, downloadedBytes / totalBytes); + } + }); + + // unbzip2-stream pulls a native crc32 accelerator as a transitive + // dependency; cross-built images (buildx BUILDPLATFORM stages) may not + // carry a binary matching the runtime architecture. Load it lazily so + // images with prebaked assets never touch this path. + let decompress: () => import("through").ThroughStream; + try { + decompress = (await import("unbzip2-stream")).default; + } catch { + throw new Error( + "SAP network extraction unavailable on this image (bzip2 decoder missing); " + + "use a prebaked release image or mount assets via BUNDLED_SAP_ASSETS", + ); + } + + // "BZh9" + the raw tail reconstructs the bz2 member the same way the + // reference implementation does. The prepended header must be a real + // byte stream (Readables concatenated as values would corrupt it). + const bz2Stream = new PrependStream(Buffer.from("BZh9", "latin1"), cdnStream); + const pipeline = bz2Stream.pipe(decompress()).pipe(skipper).pipe(extractor); + + const finished = new Promise((resolve, reject) => { + pipeline.on("finish", () => resolve()); + pipeline.on("error", (error: Error) => reject(error)); + }); + + // The extractor resolves as soon as the last wanted member is captured; + // racing it against the stream end aborts the download early. The bz2 + // stream is truncated mid-file (the package heap contains other data + // after it), so the decoder may emit a late crc error once the wanted + // members are already captured — that must never surface as an + // unhandled rejection, which crashes Node outright. + finished.catch(() => undefined); + await Promise.race([ + allFound, + finished.then(() => { + if (results.size !== SAP_ASSET_SPECS.length) { + throw new Error( + `extraction found ${results.size}/${SAP_ASSET_SPECS.length} assets`, + ); + } + }), + ]).finally(() => { + for (const segment of [extractor, skipper, bz2Stream, cdnStream]) { + segment.removeAllListeners("error"); + segment.on("error", () => undefined); + segment.destroy(); + } + }); + + if (results.size !== SAP_ASSET_SPECS.length) { + throw new Error( + `extraction found ${results.size}/${SAP_ASSET_SPECS.length} assets`, + ); + } + + for (const spec of SAP_ASSET_SPECS) { + const original = results.get(spec.name)!; + if (original.length !== spec.size) { + throw new Error(`asset ${spec.name} has unexpected size`); + } + const digest = createHash("sha256").update(original).digest("hex"); + if (digest !== spec.sha256) { + throw new Error(`asset ${spec.name} failed integrity verification`); + } + + const stripped = stripToX86_64(original); + if (!verifyStripped(spec, stripped)) { + throw new Error(`asset ${spec.name} failed post-strip verification`); + } + const target = assetPath(spec.name); + await fs.writeFile(`${target}.tmp`, stripped); + await fs.rename(`${target}.tmp`, target); + } + + state = { status: "ready" }; + } catch (error) { + state = { + status: "error", + error: error instanceof Error ? error.message : String(error), + }; + throw error; + } +} + +function httpsGetRange(url: string, start: number, end: number): Promise { + return new Promise((resolve, reject) => { + const request = https.get( + url, + { headers: { Range: `bytes=${start}-${end}` } }, + (response) => { + if (response.statusCode !== 206) { + request.destroy(); + reject(new Error(`CDN returned ${response.statusCode}`)); + return; + } + const chunks: Buffer[] = []; + response.on("data", (chunk: Buffer) => chunks.push(chunk)); + response.on("end", () => resolve(Buffer.concat(chunks))); + response.on("error", reject); + }, + ); + request.on("error", reject); + }); +} + +async function locatePayload(): Promise<{ heapOffset: number; length: number }> { + const headerAndToc = await httpsGetRange(UPDATE_URL, 0, 64 * 1024 - 1); + if (headerAndToc.subarray(0, 4).toString("latin1") !== "xar!") { + throw new Error("update package is not a xar archive"); + } + const tocLength = Number(headerAndToc.readBigUInt64BE(8)); + const toc = zlib + .inflateSync(headerAndToc.subarray(XAR_HEADER_SIZE, XAR_HEADER_SIZE + tocLength)) + .toString("utf8"); + + const blocks = toc + .split(/ /Payload<\/name>/.test(block)); + if (blocks.length !== 1) { + throw new Error("payload entry not found in xar TOC"); + } + const offsetMatch = blocks[0].match(/(0x[0-9a-f]+|\d+)<\/offset>/); + const lengthMatch = blocks[0].match(/(0x[0-9a-f]+|\d+)<\/length>/); + if (!offsetMatch || !lengthMatch) { + throw new Error("payload extent not found in xar TOC"); + } + return { + heapOffset: XAR_HEADER_SIZE + tocLength + Number(offsetMatch[1]), + length: Number(lengthMatch[1]), + }; +} + +/** A source stream that emits `prefix` before piping through `source`. */ +class PrependStream extends Readable { + private prefixPending: Buffer | null; + private sourcePiped = false; + + constructor( + prefix: Buffer, + private readonly source: Readable, + ) { + super(); + this.prefixPending = prefix; + } + + _read(): void { + if (this.prefixPending) { + const chunk = this.prefixPending; + this.prefixPending = null; + if (!this.push(chunk)) { + return; + } + } + if (!this.sourcePiped) { + this.sourcePiped = true; + this.source.on("data", (chunk: Buffer) => { + if (!this.push(chunk)) { + this.source.pause(); + } + }); + this.source.on("end", () => this.push(null)); + this.source.on("error", (error: Error) => this.destroy(error)); + return; + } + this.source.resume(); + } + + _destroy(error: Error | null, callback: (error?: Error | null) => void): void { + this.source.destroy(); + callback(error); + } +} + +/** Streams bytes [start, end) from the CDN in sequential range requests. */ +function rangeStream( + start: number, + end: number, + onChunk?: (bytes: number) => void, +): Readable { + const chunkSize = 16 << 20; + let offset = start; + let pending: Promise | null = null; + + return new Readable({ + async read() { + try { + for (;;) { + if (offset >= end) { + this.push(null); + return; + } + if (!pending) { + const from = offset; + const to = Math.min(from + chunkSize, end) - 1; + pending = httpsGetRange(UPDATE_URL, from, to).then((buffer) => { + offset = to + 1; + onChunk?.(buffer.length); + return buffer; + }); + } + const buffer = await pending; + pending = null; + if (!this.push(buffer)) { + return; + } + } + } catch (error) { + this.destroy(error instanceof Error ? error : new Error(String(error))); + } + }, + }); +} + +/** Drops the first `count` bytes of the stream. */ +class SkipStream extends Transform { + private skipped = 0; + + constructor(private readonly count: number) { + super(); + } + + _transform( + chunk: Buffer, + _encoding: string, + callback: (error?: Error | null, data?: Buffer) => void, + ): void { + if (this.skipped < this.count) { + const remaining = this.count - this.skipped; + if (chunk.length <= remaining) { + this.skipped += chunk.length; + callback(null); + return; + } + this.skipped = this.count; + callback(null, chunk.subarray(remaining)); + return; + } + callback(null, chunk); + } +} + +/** + * Parses cpio members (odc "070707" and newc "070701" formats) and captures + * the wanted files. macOS package payloads historically use the portable + * ASCII odc format: 76-byte headers, octal text fields, no padding. + */ +class CpioExtractor extends Transform { + private buffer: Buffer = Buffer.alloc(0); + private found = 0; + private format: "odc" | "newc" | null = null; + + constructor( + private readonly wanted: Map void>, + private readonly onAllFound: () => void, + ) { + super(); + } + + _transform( + chunk: Buffer, + _encoding: string, + callback: (error?: Error | null) => void, + ): void { + this.buffer = Buffer.concat([this.buffer, chunk]); + try { + while (this.parseOne()) { + if (this.found === this.wanted.size) { + this.onAllFound(); + this.push(null); + callback(null); + return; + } + } + callback(null); + } catch (error) { + callback(error instanceof Error ? error : new Error(String(error))); + } + } + + /** Parses one member if the buffer holds it completely. */ + private parseOne(): boolean { + if (this.buffer.length < 6) { + return false; + } + const magic = this.buffer.subarray(0, 6).toString("latin1"); + if (!this.format) { + if (magic === "070707") { + this.format = "odc"; + } else if (magic === "070701") { + this.format = "newc"; + } else { + throw new Error(`unexpected cpio magic: ${magic}`); + } + } + + if (this.format === "odc") { + return this.parseOdc(); + } + return this.parseNewc(); + } + + /** odc: 76-byte header, octal ASCII fields, name and data unpadded. */ + private parseOdc(): boolean { + const HEADER_SIZE = 76; + if (this.buffer.length < HEADER_SIZE) { + return false; + } + const octal = (start: number, length: number): number => + parseInt(this.buffer.subarray(start, start + length).toString("latin1"), 8); + + const nameSize = octal(59, 6); + const fileSize = octal(65, 11); + const nameEnd = HEADER_SIZE + nameSize - 1; + if (this.buffer.length < nameEnd + 1) { + return false; + } + const name = this.buffer.subarray(HEADER_SIZE, nameEnd).toString("latin1"); + const dataStart = HEADER_SIZE + nameSize; + const dataEnd = dataStart + fileSize; + if (this.buffer.length < dataEnd) { + return false; + } + + const handler = this.wanted.get(name); + if (handler) { + handler(this.buffer.subarray(dataStart, dataEnd)); + this.found += 1; + } + this.buffer = this.buffer.subarray(dataEnd); + return name !== "TRAILER!!!"; + } + + /** newc: 110-byte header, hex ASCII fields, 4-byte alignment. */ + private parseNewc(): boolean { + const HEADER_SIZE = 110; + if (this.buffer.length < HEADER_SIZE) { + return false; + } + const field = (offset: number): number => + parseInt( + this.buffer.subarray(offset, offset + 8).toString("latin1"), + 16, + ); + + const fileSize = field(54); + const nameSize = field(94); + const nameEnd = HEADER_SIZE + nameSize - 1; + if (this.buffer.length < nameEnd + 1) { + return false; + } + const name = this.buffer + .subarray(HEADER_SIZE, nameEnd) + .toString("latin1"); + const dataStart = Math.ceil((HEADER_SIZE + nameSize) / 4) * 4; + const dataEnd = dataStart + fileSize; + if (this.buffer.length < dataEnd) { + return false; + } + + const handler = this.wanted.get(name); + if (handler) { + handler(this.buffer.subarray(dataStart, dataEnd)); + this.found += 1; + } + this.buffer = this.buffer.subarray( + dataStart + Math.ceil(fileSize / 4) * 4, + ); + return name !== "TRAILER!!!"; + } +} diff --git a/frontend/scripts/unicorn-wasm-patch/build.sh b/frontend/scripts/unicorn-wasm-patch/build.sh new file mode 100755 index 00000000..d4c0d1bb --- /dev/null +++ b/frontend/scripts/unicorn-wasm-patch/build.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Builds the Unicorn TCI WASM engine used by the browser-side SAP signer. +# +# Unicorn 2.x only ships JIT TCG backends, which cannot run under WebAssembly. +# This script clones Unicorn 2.1.4, applies patches/unicorn-2.1.4-tci-wasm.patch +# (restores the QEMU 5.0 TCI interpreter, forces 64-bit virtual registers on +# wasm32, adds uniform-signature helper trampolines, and adapts glib- compat +# comparators for wasm's strict indirect-call checks), then cross-compiles with +# emscripten and links the JS glue into: +# ../src/apple/sap/vendor/unicorn.mjs + unicorn.wasm +# +# Requirements: docker (emscripten runs in a container). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")" && pwd)" +VENDOR_DIR="$ROOT/../../src/apple/sap/vendor" +SRC="$ROOT/unicorn-src" +UC_VERSION="2.1.4" + +if [ ! -d "$SRC" ]; then + git clone --depth 1 --branch "$UC_VERSION" https://github.com/unicorn-engine/unicorn.git "$SRC" +fi + +if [ ! -f "$SRC/.tci-wasm-patched" ]; then + git -C "$SRC" apply "$ROOT/patches/unicorn-2.1.4-tci-wasm.patch" + touch "$SRC/.tci-wasm-patched" +fi + +mkdir -p "$VENDOR_DIR" + +docker run --rm -v "$ROOT:/work" -w /work emscripten/emsdk:3.1.61 bash -exc ' + apt-get update -qq >/dev/null 2>&1 || true + apt-get install -y -qq pkg-config >/dev/null 2>&1 || true + mkdir -p build dist && cd build + + emcmake cmake ../unicorn-src \ + -DCMAKE_BUILD_TYPE=Release \ + -DUNICORN_ARCH="x86" \ + -DCMAKE_C_FLAGS="-fPIC -O3" + + emmake make -j"$(nproc)" unicorn + + emcc ../glue.c -I../unicorn-src/include \ + libunicorn.a libunicorn-common.a libx86_64-softmmu.a \ + -o ../dist/unicorn.mjs \ + -O3 \ + -sMODULARIZE=1 \ + -sEXPORT_NAME=UnicornModule \ + -sALLOW_MEMORY_GROWTH=1 \ + -sMAXIMUM_MEMORY=4GB \ + -sINITIAL_MEMORY=64MB \ + -sALLOW_TABLE_GROWTH=1 \ + -sENVIRONMENT=web,worker,node \ + -sEXPORTED_FUNCTIONS=_uc2_open,_uc2_close,_uc2_strerror,_uc2_version,_uc2_mem_map,_uc2_mem_unmap,_uc2_mem_protect,_uc2_mem_write,_uc2_mem_read,_uc2_reg_write,_uc2_reg_read,_uc2_emu_start,_uc2_emu_stop,_uc2_hook_add_code,_uc2_hook_del,_uc2_set_code_hook_cb,_uc2_hook_add_mem_invalid,_uc2_set_mem_hook_cb,_uc2_scratch_alloc,_uc2_scratch_free,_malloc,_free \ + -sEXPORT_ES6=1 \ + -sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction,getValue,setValue,HEAPU8,lengthBytesUTF,stringToUTF8,UTF8ToString \ + --no-entry +' + +cp dist/unicorn.mjs dist/unicorn.wasm "$VENDOR_DIR/" +echo "engine built into src/apple/sap/vendor/" diff --git a/frontend/scripts/unicorn-wasm-patch/glue.c b/frontend/scripts/unicorn-wasm-patch/glue.c new file mode 100644 index 00000000..10f515e8 --- /dev/null +++ b/frontend/scripts/unicorn-wasm-patch/glue.c @@ -0,0 +1,181 @@ +/* JS-friendly Unicorn 2.x glue for emscripten. + * + * All 64-bit guest addresses cross the JS boundary as `double` (lossless for + * integers < 2^53; SAP guest addresses stay below 2^48). Buffers are passed as + * wasm-heap offsets allocated by JS via uc2_scratch_alloc. + * + * The code hook is registered in C; the C thunk converts the uint64 address to + * double and forwards to a JS function pointer with signature (double, int), + * avoiding i64-at-the-JS-boundary entirely. + */ +#include +#include +#include +#include +#include + +typedef void (*js_hook_cb)(double address, int size); +typedef double (*js_mem_hook_cb)(double type, double address, double size, double value); + + +static js_hook_cb g_code_cb = NULL; + +static void code_hook_thunk(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +{ + (void)uc; + (void)user_data; + if (g_code_cb != NULL) { + g_code_cb((double)address, (int)size); + } +} + +double uc2_open(int arch, int mode) +{ + uc_engine *uc = NULL; + if (uc_open((uc_arch)arch, (uc_mode)mode, &uc) != UC_ERR_OK) { + return 0; + } + return (double)(uintptr_t)uc; +} + +int uc2_close(double uc) +{ + return uc_close((uc_engine *)(uintptr_t)uc); +} + +const char *uc2_strerror(int code) +{ + return uc_strerror((uc_err)code); +} + +int uc2_version(int *major, int *minor) +{ + return uc_version(major, minor); +} + +int uc2_mem_map(double uc, double address, double size) +{ + return uc_mem_map((uc_engine *)(uintptr_t)uc, (uint64_t)address, (size_t)size, UC_PROT_ALL); +} + +int uc2_mem_unmap(double uc, double address, double size) +{ + return uc_mem_unmap((uc_engine *)(uintptr_t)uc, (uint64_t)address, (size_t)size); +} + +int uc2_mem_protect(double uc, double address, double size, int perms) +{ + return uc_mem_protect((uc_engine *)(uintptr_t)uc, (uint64_t)address, (size_t)size, (uint32_t)perms); +} + +int uc2_mem_write(double uc, double address, int buffer_offset, int length) +{ + uint8_t *base = (uint8_t *)malloc(length); + if (base == NULL) { + return -99; + } + memcpy(base, (uint8_t *)(uintptr_t)buffer_offset, (size_t)length); + int rc = uc_mem_write((uc_engine *)(uintptr_t)uc, (uint64_t)address, base, (size_t)length); + free(base); + return rc; +} + +int uc2_mem_read(double uc, double address, int buffer_offset, int length) +{ + return uc_mem_read((uc_engine *)(uintptr_t)uc, (uint64_t)address, (uint8_t *)(uintptr_t)buffer_offset, (size_t)length); +} + +int uc2_reg_write(double uc, int regid, double value) +{ + uint64_t raw = (uint64_t)value; + return uc_reg_write((uc_engine *)(uintptr_t)uc, regid, &raw); +} + +double uc2_reg_read(double uc, int regid) +{ + uint64_t raw = 0; + if (uc_reg_read((uc_engine *)(uintptr_t)uc, regid, &raw) != UC_ERR_OK) { + return -1; + } + return (double)raw; +} + +extern double tci_wasm_deadline_ms; + +int uc2_emu_start(double uc, double begin, double until, double timeout_us, double count) +{ + if (timeout_us > 0) { + tci_wasm_deadline_ms = emscripten_get_now() + timeout_us / 1000.0; + } else { + tci_wasm_deadline_ms = 0; + } + return uc_emu_start((uc_engine *)(uintptr_t)uc, (uint64_t)begin, (uint64_t)until, (uint64_t)timeout_us, (size_t)count); +} + +int uc2_emu_stop(double uc) +{ + return uc_emu_stop((uc_engine *)(uintptr_t)uc); +} + +double uc2_hook_add_code(double uc, double begin, double end) +{ + uc_hook handle = 0; + if (uc_hook_add((uc_engine *)(uintptr_t)uc, &handle, UC_HOOK_CODE, (void *)code_hook_thunk, NULL, + (uint64_t)begin, (uint64_t)end) != UC_ERR_OK) { + return 0; + } + return (double)(uintptr_t)handle; +} + +int uc2_hook_del(double uc, double handle) +{ + return uc_hook_del((uc_engine *)(uintptr_t)uc, (uc_hook)(uintptr_t)handle); +} + +void uc2_set_code_hook_cb(int fp_index) +{ + g_code_cb = (js_hook_cb)(uintptr_t)fp_index; +} + +static js_mem_hook_cb g_mem_cb = NULL; + +static int mem_invalid_thunk(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data) +{ + (void)uc; + (void)user_data; + if (g_mem_cb != NULL) { + return (int)g_mem_cb((double)type, (double)address, (double)size, (double)value); + } + return 0; +} + +double uc2_hook_add_mem_invalid(double uc) +{ + uc_hook handle = 0; + if (uc_hook_add((uc_engine *)(uintptr_t)uc, &handle, UC_HOOK_MEM_INVALID, + (void *)mem_invalid_thunk, NULL, 1, 0) != UC_ERR_OK) { + return 0; + } + return (double)(uintptr_t)handle; +} + +void uc2_set_mem_hook_cb(int fp_index) +{ + g_mem_cb = (js_mem_hook_cb)(uintptr_t)fp_index; +} + +int uc2_scratch_alloc(int length) +{ + void *block = malloc((size_t)length); + if (block == NULL) { + return 0; + } + return (int)(uintptr_t)block; +} + +void uc2_scratch_free(int offset) +{ + free((void *)(uintptr_t)offset); +} diff --git a/frontend/scripts/unicorn-wasm-patch/patches/unicorn-2.1.4-tci-wasm.patch b/frontend/scripts/unicorn-wasm-patch/patches/unicorn-2.1.4-tci-wasm.patch new file mode 100644 index 00000000..da5ce489 --- /dev/null +++ b/frontend/scripts/unicorn-wasm-patch/patches/unicorn-2.1.4-tci-wasm.patch @@ -0,0 +1,3085 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 033db09..4dbb92d 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -182,6 +182,10 @@ else() + elseif(UC_COMPILER_VERSION MATCHES "^aarch64.*") + set(UNICORN_TARGET_ARCH "aarch64") + endif() ++ elseif(EMSCRIPTEN) ++ # No native TCG backend can execute under WebAssembly; use the ++ # TCI interpreter backend restored from qemu 5.0.1. ++ set(UNICORN_TARGET_ARCH "tci") + elseif(ANDROID_ABI) + string(FIND "${ANDROID_ABI}" "arm64" UC_RET) + file(WRITE ${CMAKE_BINARY_DIR}/adb.sh "#!/bin/bash\n\n# Auto-generated by CMakeLists.txt\n\nadb shell mkdir -p /data/local/tmp/build\n") +@@ -380,6 +384,9 @@ else() + ${TARGET_LIST} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) ++ if(UNICORN_TARGET_ARCH STREQUAL "tci") ++ file(APPEND ${CMAKE_BINARY_DIR}/config-host.mak "CONFIG_TCG_INTERPRETER=y\n") ++ endif() + execute_process(COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/qemu/scripts/create_config + INPUT_FILE ${CMAKE_BINARY_DIR}/config-host.mak + OUTPUT_FILE ${CMAKE_BINARY_DIR}/config-host.h +@@ -519,13 +526,19 @@ set(UNICORN_ARCH_COMMON + qemu/softmmu/unicorn_vtlb.c + ) + ++if(UNICORN_TARGET_ARCH STREQUAL "tci") ++ list(APPEND UNICORN_ARCH_COMMON qemu/tcg/tci.c) ++endif() ++ + if(UNICORN_HAS_X86) + add_library(x86_64-softmmu STATIC + ${UNICORN_ARCH_COMMON} + + qemu/hw/i386/x86.c + ++ + qemu/target/i386/arch_memory_mapping.c ++ qemu/target/i386/tci-wasm-tramp.c + qemu/target/i386/bpt_helper.c + qemu/target/i386/cc_helper.c + qemu/target/i386/cpu.c +diff --git a/glib_compat/gtree.c b/glib_compat/gtree.c +index b2617a3..b188693 100644 +--- a/glib_compat/gtree.c ++++ b/glib_compat/gtree.c +@@ -75,6 +75,18 @@ typedef struct _GTreeNode GTreeNode; + * [balanced binary tree][glib-Balanced-Binary-Trees]. It should be + * accessed only by using the following functions. + */ ++#ifdef __EMSCRIPTEN__ ++/* Adapt a 2-arg GCompareFunc to GCompareDataFunc; the real function pointer ++ rides in key_compare_data so it is invoked with its true wasm signature. ++ wasm enforces indirect-call signatures, so C's traditional cast of a ++ 2-argument comparator into a 3-argument GCompareDataFunc would trap. */ ++static gint gtree_passthrough_data_compare (gconstpointer a, gconstpointer b, ++ gpointer user_data) ++{ ++ return ((GCompareFunc) user_data) (a, b); ++} ++#endif ++ + struct _GTree + { + GTreeNode *root; +@@ -160,8 +172,13 @@ GTree *g_tree_new (GCompareFunc key_compare_func) + { + g_return_val_if_fail (key_compare_func != NULL, NULL); + ++#ifdef __EMSCRIPTEN__ ++ return g_tree_new_full (gtree_passthrough_data_compare, ++ (gpointer) key_compare_func, NULL, NULL); ++#else + return g_tree_new_full ((GCompareDataFunc) key_compare_func, NULL, + NULL, NULL); ++#endif + } + + /** +diff --git a/qemu/configure b/qemu/configure +index cfd344f..a537cae 100755 +--- a/qemu/configure ++++ b/qemu/configure +@@ -396,7 +396,10 @@ int main(void) { return 0; } + EOF + } + +-if check_define __linux__ ; then ++if echo "$cc" | grep -q "emcc"; then ++ # Emscripten no longer predefines __linux__; it models a POSIX/Linux host. ++ targetos="Linux" ++elif check_define __linux__ ; then + targetos="Linux" + elif check_define _WIN32 ; then + targetos='MINGW32' +@@ -848,8 +851,13 @@ case "$cpu" in + # ??? Only extremely old AMD cpus do not have cmpxchg16b. + # If we truly care, we should simply detect this case at + # runtime and generate the fallback to serial emulation. +- CPU_CFLAGS="-m64 -mcx16" +- QEMU_LDFLAGS="-m64 $QEMU_LDFLAGS" ++ if echo "$cc" | grep -q "emcc"; then ++ # wasm32 has no cmpxchg16b; the host TCG backend is TCI anyway. ++ CPU_CFLAGS="" ++ else ++ CPU_CFLAGS="-m64 -mcx16" ++ QEMU_LDFLAGS="-m64 $QEMU_LDFLAGS" ++ fi + ;; + loongarch*) + CPU_CFLAGS="" +diff --git a/qemu/include/exec/exec-all.h b/qemu/include/exec/exec-all.h +index 68c6567..e8dea74 100644 +--- a/qemu/include/exec/exec-all.h ++++ b/qemu/include/exec/exec-all.h +@@ -395,6 +395,9 @@ void tb_exec_unlock(struct uc_struct*); + #ifdef _MSC_VER + #include + # define GETPC() (uintptr_t)_ReturnAddress() ++#elif defined(CONFIG_TCG_INTERPRETER) ++extern uintptr_t tci_tb_ptr; ++# define GETPC() tci_tb_ptr + #else + # define GETPC() \ + ((uintptr_t)__builtin_extract_return_addr(__builtin_return_address(0))) +diff --git a/qemu/include/exec/helper-gen.h b/qemu/include/exec/helper-gen.h +index c77990f..d45712a 100644 +--- a/qemu/include/exec/helper-gen.h ++++ b/qemu/include/exec/helper-gen.h +@@ -6,10 +6,43 @@ + + #include "exec/helper-head.h" + ++#ifdef __EMSCRIPTEN__ ++/* ++ * wasm: TCI invokes helpers through a single cast signature, which wasm's ++ * strict indirect-call checks reject. Redirect every generated call to a ++ * uniform-signature trampoline (defined in target//tci-wasm-tramp.c). ++ * Pre-pass: declare trampolines for the whole helper list. Plain DEF_HELPER_N ++ * entries are aliases of the FLAGS variants defined in helper-head.h, so they ++ * delegate automatically and must not be touched here. ++ */ ++#define TCI_WASM_TRAMP_PROTO(name) \ ++ uint64_t glue(tci_wasm_tramp_, name)(uint64_t, uint64_t, uint64_t, \ ++ uint64_t, uint64_t, uint64_t) ++#define TCI_WASM_CALL_TARGET(name) ((void *)(uintptr_t)glue(tci_wasm_tramp_, name)) ++#define DEF_HELPER_FLAGS_0(name, flags, ret) TCI_WASM_TRAMP_PROTO(name); ++#define DEF_HELPER_FLAGS_1(name, flags, ret, t1) TCI_WASM_TRAMP_PROTO(name); ++#define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2) TCI_WASM_TRAMP_PROTO(name); ++#define DEF_HELPER_FLAGS_3(name, flags, ret, t1, t2, t3) TCI_WASM_TRAMP_PROTO(name); ++#define DEF_HELPER_FLAGS_4(name, flags, ret, t1, t2, t3, t4) TCI_WASM_TRAMP_PROTO(name); ++#define DEF_HELPER_FLAGS_5(name, flags, ret, t1, t2, t3, t4, t5) TCI_WASM_TRAMP_PROTO(name); ++#define DEF_HELPER_FLAGS_6(name, flags, ret, t1, t2, t3, t4, t5, t6) TCI_WASM_TRAMP_PROTO(name); ++#define DEF_HELPER_FLAGS_7(name, flags, ret, t1, t2, t3, t4, t5, t6, t7) TCI_WASM_TRAMP_PROTO(name); ++#include "helper.h" ++#include "accel/tcg/tcg-runtime.h" ++#undef DEF_HELPER_FLAGS_0 ++#undef DEF_HELPER_FLAGS_1 ++#undef DEF_HELPER_FLAGS_2 ++#undef DEF_HELPER_FLAGS_3 ++#undef DEF_HELPER_FLAGS_4 ++#undef DEF_HELPER_FLAGS_5 ++#undef DEF_HELPER_FLAGS_6 ++#undef DEF_HELPER_FLAGS_7 ++#endif /* __EMSCRIPTEN__ */ ++ + #define DEF_HELPER_FLAGS_0(name, flags, ret) \ + static inline void glue(gen_helper_, name)(TCGContext *tcg_ctx, dh_retvar_decl0(ret)) \ + { \ +- tcg_gen_callN(tcg_ctx, HELPER(name), dh_retvar(ret), 0, NULL); \ ++ tcg_gen_callN(tcg_ctx, TCI_WASM_CALL_TARGET(name), dh_retvar(ret), 0, NULL); \ + } + + #define DEF_HELPER_FLAGS_1(name, flags, ret, t1) \ +@@ -17,7 +50,7 @@ static inline void glue(gen_helper_, name)(TCGContext *tcg_ctx, dh_retvar_decl(r + dh_arg_decl(t1, 1)) \ + { \ + TCGTemp *args[1] = { dh_arg(t1, 1) }; \ +- tcg_gen_callN(tcg_ctx, HELPER(name), dh_retvar(ret), 1, args); \ ++ tcg_gen_callN(tcg_ctx, TCI_WASM_CALL_TARGET(name), dh_retvar(ret), 1, args); \ + } + + #define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2) \ +@@ -25,7 +58,7 @@ static inline void glue(gen_helper_, name)(TCGContext *tcg_ctx, dh_retvar_decl(r + dh_arg_decl(t1, 1), dh_arg_decl(t2, 2)) \ + { \ + TCGTemp *args[2] = { dh_arg(t1, 1), dh_arg(t2, 2) }; \ +- tcg_gen_callN(tcg_ctx, HELPER(name), dh_retvar(ret), 2, args); \ ++ tcg_gen_callN(tcg_ctx, TCI_WASM_CALL_TARGET(name), dh_retvar(ret), 2, args); \ + } + + #define DEF_HELPER_FLAGS_3(name, flags, ret, t1, t2, t3) \ +@@ -33,7 +66,7 @@ static inline void glue(gen_helper_, name)(TCGContext *tcg_ctx, dh_retvar_decl(r + dh_arg_decl(t1, 1), dh_arg_decl(t2, 2), dh_arg_decl(t3, 3)) \ + { \ + TCGTemp *args[3] = { dh_arg(t1, 1), dh_arg(t2, 2), dh_arg(t3, 3) }; \ +- tcg_gen_callN(tcg_ctx, HELPER(name), dh_retvar(ret), 3, args); \ ++ tcg_gen_callN(tcg_ctx, TCI_WASM_CALL_TARGET(name), dh_retvar(ret), 3, args); \ + } + + #define DEF_HELPER_FLAGS_4(name, flags, ret, t1, t2, t3, t4) \ +@@ -43,7 +76,7 @@ static inline void glue(gen_helper_, name)(TCGContext *tcg_ctx, dh_retvar_decl(r + { \ + TCGTemp *args[4] = { dh_arg(t1, 1), dh_arg(t2, 2), \ + dh_arg(t3, 3), dh_arg(t4, 4) }; \ +- tcg_gen_callN(tcg_ctx, HELPER(name), dh_retvar(ret), 4, args); \ ++ tcg_gen_callN(tcg_ctx, TCI_WASM_CALL_TARGET(name), dh_retvar(ret), 4, args); \ + } + + #define DEF_HELPER_FLAGS_5(name, flags, ret, t1, t2, t3, t4, t5) \ +@@ -53,7 +86,7 @@ static inline void glue(gen_helper_, name)(TCGContext *tcg_ctx, dh_retvar_decl(r + { \ + TCGTemp *args[5] = { dh_arg(t1, 1), dh_arg(t2, 2), dh_arg(t3, 3), \ + dh_arg(t4, 4), dh_arg(t5, 5) }; \ +- tcg_gen_callN(tcg_ctx, HELPER(name), dh_retvar(ret), 5, args); \ ++ tcg_gen_callN(tcg_ctx, TCI_WASM_CALL_TARGET(name), dh_retvar(ret), 5, args); \ + } + + #define DEF_HELPER_FLAGS_6(name, flags, ret, t1, t2, t3, t4, t5, t6) \ +@@ -63,7 +96,7 @@ static inline void glue(gen_helper_, name)(TCGContext *tcg_ctx, dh_retvar_decl(r + { \ + TCGTemp *args[6] = { dh_arg(t1, 1), dh_arg(t2, 2), dh_arg(t3, 3), \ + dh_arg(t4, 4), dh_arg(t5, 5), dh_arg(t6, 6) }; \ +- tcg_gen_callN(tcg_ctx, HELPER(name), dh_retvar(ret), 6, args); \ ++ tcg_gen_callN(tcg_ctx, TCI_WASM_CALL_TARGET(name), dh_retvar(ret), 6, args); \ + } + + #define DEF_HELPER_FLAGS_7(name, flags, ret, t1, t2, t3, t4, t5, t6, t7)\ +@@ -75,7 +108,7 @@ static inline void glue(gen_helper_, name)(TCGContext *tcg_ctx, dh_retvar_decl(r + TCGTemp *args[7] = { dh_arg(t1, 1), dh_arg(t2, 2), dh_arg(t3, 3), \ + dh_arg(t4, 4), dh_arg(t5, 5), dh_arg(t6, 6), \ + dh_arg(t7, 7) }; \ +- tcg_gen_callN(tcg_ctx, HELPER(name), dh_retvar(ret), 7, args); \ ++ tcg_gen_callN(tcg_ctx, TCI_WASM_CALL_TARGET(name), dh_retvar(ret), 7, args); \ + } + + #include "helper.h" +diff --git a/qemu/include/qemu/int128.h b/qemu/include/qemu/int128.h +index d1d1ad4..0654706 100644 +--- a/qemu/include/qemu/int128.h ++++ b/qemu/include/qemu/int128.h +@@ -146,7 +146,7 @@ static inline Int128 bswap128(Int128 a) + #else /* !CONFIG_INT128 */ + + typedef struct Int128 Int128; +-#if !(defined(_MSC_VER) && defined(__clang__)) ++#if !(defined(_MSC_VER) || defined(__clang__)) + typedef Int128 __int128_t; + #endif + +diff --git a/qemu/include/tcg/tcg-op.h b/qemu/include/tcg/tcg-op.h +index 93026d1..63a2957 100644 +--- a/qemu/include/tcg/tcg-op.h ++++ b/qemu/include/tcg/tcg-op.h +@@ -47,7 +47,13 @@ static inline void gen_uc_tracecode(TCGContext *tcg_ctx, int32_t size, int32_t t + }; + + const int hook_type = type & UC_HOOK_IDX_MASK; ++#ifdef __EMSCRIPTEN__ ++ /* wasm cannot inline arbitrary C callbacks as TCG helpers (TCI calls ++ them through a uniform signature); always use the tracecode helper. */ ++ if (0) { ++#else + if (puc->hooks_count[hook_type] == 1 && !(type & UC_HOOK_FLAG_NO_STOP)) { ++#endif + cur = puc->hook[hook_type].head; + + while (cur) { +diff --git a/qemu/target/i386/tci-wasm-tramp.c b/qemu/target/i386/tci-wasm-tramp.c +new file mode 100644 +index 0000000..c70aa68 +--- /dev/null ++++ b/qemu/target/i386/tci-wasm-tramp.c +@@ -0,0 +1,134 @@ ++/* ++ * Uniform-signature trampolines for TCI helpers under WebAssembly. ++ * ++ * With TCI running 64-bit virtual registers (see tcg/tci/tcg-target.h), the ++ * interpreter invokes every helper through uint64_t (*)(uint64_t x6). Native ++ * ABIs tolerate TCI's cast; wasm's strict indirect-call checks do not, so each ++ * helper called from generated code goes through a trampoline with exactly ++ * that signature. Each helper argument occupies exactly one register slot in ++ * this mode, so trampolins simply forward a0..a5 with the proper casts. ++ */ ++#include "qemu/osdep.h" ++#include "cpu.h" ++ ++/* Real helper prototypes first: helper-proto.h declares them and undefines ++ its DEF_HELPER macros afterwards. */ ++#include "exec/helper-proto.h" ++ ++/* Argument extraction: one u64 register per parameter in 64-bit TCI. */ ++#define TW_A_env a0 ++#define TW_A_ptr a0 ++#define TW_A_Reg a0 ++#define TW_A_ZMMReg a0 ++#define TW_A_MMXReg a0 ++#define TW_A_cptr a0 ++#define TW_A_int a0 ++#define TW_A_i32 a0 ++#define TW_A_s32 a0 ++#define TW_A_i64 a0 ++#define TW_A_s64 a0 ++#define TW_A_tl a0 ++ ++/* Final cast of the register value to the real parameter type. */ ++#define TW_C_env(p) ((CPUArchState *)(uintptr_t)(p)) ++#define TW_C_ptr(p) ((void *)(uintptr_t)(p)) ++#define TW_C_Reg(p) ((Reg *)(uintptr_t)(p)) ++#define TW_C_ZMMReg(p) ((ZMMReg *)(uintptr_t)(p)) ++#define TW_C_MMXReg(p) ((MMXReg *)(uintptr_t)(p)) ++#define TW_C_cptr(p) ((const void *)(uintptr_t)(p)) ++#define TW_C_int(p) ((int)(uint32_t)(p)) ++#define TW_C_i32(p) ((int32_t)(uint32_t)(p)) ++#define TW_C_s32(p) ((int32_t)(uint32_t)(p)) ++#define TW_C_i64(p) ((uint64_t)(p)) ++#define TW_C_s64(p) ((int64_t)(p)) ++#define TW_C_tl(p) ((target_ulong)(p)) ++ ++/* Return widening. */ ++#define TW_R_void(e) ((void)(e), (uint64_t)0) ++#define TW_R_int(e) ((uint64_t)(uint32_t)(e)) ++#define TW_R_i32(e) ((uint64_t)(uint32_t)(e)) ++#define TW_R_s32(e) ((uint64_t)(uint32_t)(e)) ++#define TW_R_i64(e) ((uint64_t)(e)) ++#define TW_R_s64(e) ((uint64_t)(e)) ++#define TW_R_tl(e) ((uint64_t)(e)) ++#define TW_R_ptr(e) ((uint64_t)(uintptr_t)(e)) ++#define TW_R_noreturn(e) ((void)(e), (uint64_t)0) ++ ++#define TW_PARAMS uint64_t a0, uint64_t a1, uint64_t a2, \ ++ uint64_t a3, uint64_t a4, uint64_t a5 ++ ++#define DEF_HELPER_FLAGS_0(name, flags, ret) \ ++uint64_t glue(tci_wasm_tramp_, name)(TW_PARAMS) \ ++{ \ ++ (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; \ ++ return TW_R_##ret(glue(helper_, name)()); \ ++} ++ ++#define DEF_HELPER_FLAGS_1(name, flags, ret, t1) \ ++uint64_t glue(tci_wasm_tramp_, name)(TW_PARAMS) \ ++{ \ ++ (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; \ ++ return TW_R_##ret(glue(helper_, name)(TW_C_##t1(a0))); \ ++} ++ ++#define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2) \ ++uint64_t glue(tci_wasm_tramp_, name)(TW_PARAMS) \ ++{ \ ++ (void)a2; (void)a3; (void)a4; (void)a5; \ ++ return TW_R_##ret(glue(helper_, name)(TW_C_##t1(a0), \ ++ TW_C_##t2(a1))); \ ++} ++ ++#define DEF_HELPER_FLAGS_3(name, flags, ret, t1, t2, t3) \ ++uint64_t glue(tci_wasm_tramp_, name)(TW_PARAMS) \ ++{ \ ++ (void)a3; (void)a4; (void)a5; \ ++ return TW_R_##ret(glue(helper_, name)(TW_C_##t1(a0), \ ++ TW_C_##t2(a1), \ ++ TW_C_##t3(a2))); \ ++} ++ ++#define DEF_HELPER_FLAGS_4(name, flags, ret, t1, t2, t3, t4) \ ++uint64_t glue(tci_wasm_tramp_, name)(TW_PARAMS) \ ++{ \ ++ (void)a4; (void)a5; \ ++ return TW_R_##ret(glue(helper_, name)(TW_C_##t1(a0), \ ++ TW_C_##t2(a1), \ ++ TW_C_##t3(a2), \ ++ TW_C_##t4(a3))); \ ++} ++ ++#define DEF_HELPER_FLAGS_5(name, flags, ret, t1, t2, t3, t4, t5) \ ++uint64_t glue(tci_wasm_tramp_, name)(TW_PARAMS) \ ++{ \ ++ (void)a5; \ ++ return TW_R_##ret(glue(helper_, name)(TW_C_##t1(a0), \ ++ TW_C_##t2(a1), \ ++ TW_C_##t3(a2), \ ++ TW_C_##t4(a3), \ ++ TW_C_##t5(a4))); \ ++} ++ ++#define DEF_HELPER_FLAGS_6(name, flags, ret, t1, t2, t3, t4, t5, t6) \ ++uint64_t glue(tci_wasm_tramp_, name)(TW_PARAMS) \ ++{ \ ++ return TW_R_##ret(glue(helper_, name)(TW_C_##t1(a0), \ ++ TW_C_##t2(a1), \ ++ TW_C_##t3(a2), \ ++ TW_C_##t4(a3), \ ++ TW_C_##t5(a4), \ ++ TW_C_##t6(a5))); \ ++} ++ ++#define DEF_HELPER_0(name, ret) DEF_HELPER_FLAGS_0(name, 0, ret) ++#define DEF_HELPER_1(name, ret, t1) DEF_HELPER_FLAGS_1(name, 0, ret, t1) ++#define DEF_HELPER_2(name, ret, t1, t2) DEF_HELPER_FLAGS_2(name, 0, ret, t1, t2) ++#define DEF_HELPER_3(name, ret, t1, t2, t3) DEF_HELPER_FLAGS_3(name, 0, ret, t1, t2, t3) ++#define DEF_HELPER_4(name, ret, t1, t2, t3, t4) DEF_HELPER_FLAGS_4(name, 0, ret, t1, t2, t3, t4) ++#define DEF_HELPER_5(name, ret, t1, t2, t3, t4, t5) DEF_HELPER_FLAGS_5(name, 0, ret, t1, t2, t3, t4, t5) ++#define DEF_HELPER_6(name, ret, t1, t2, t3, t4, t5, t6) DEF_HELPER_FLAGS_6(name, 0, ret, t1, t2, t3, t4, t5, t6) ++ ++/* Regenerate the full helper list (target helpers + TCG runtime helpers), ++ this time as trampolines. */ ++#include "helper.h" ++#include "accel/tcg/tcg-runtime.h" +diff --git a/qemu/tcg/tci/README b/qemu/tcg/tci/README +new file mode 100644 +index 0000000..386c3c7 +--- /dev/null ++++ b/qemu/tcg/tci/README +@@ -0,0 +1,130 @@ ++TCG Interpreter (TCI) - Copyright (c) 2011 Stefan Weil. ++ ++This file is released under the BSD license. ++ ++1) Introduction ++ ++TCG (Tiny Code Generator) is a code generator which translates ++code fragments ("basic blocks") from target code (any of the ++targets supported by QEMU) to a code representation which ++can be run on a host. ++ ++QEMU can create native code for some hosts (arm, i386, ia64, ppc, ppc64, ++s390, sparc, x86_64). For others, unofficial host support was written. ++ ++By adding a code generator for a virtual machine and using an ++interpreter for the generated bytecode, it is possible to ++support (almost) any host. ++ ++This is what TCI (Tiny Code Interpreter) does. ++ ++2) Implementation ++ ++Like each TCG host frontend, TCI implements the code generator in ++tcg-target.inc.c, tcg-target.h. Both files are in directory tcg/tci. ++ ++The additional file tcg/tci.c adds the interpreter. ++ ++The bytecode consists of opcodes (same numeric values as those used by ++TCG), command length and arguments of variable size and number. ++ ++3) Usage ++ ++For hosts without native TCG, the interpreter TCI must be enabled by ++ ++ configure --enable-tcg-interpreter ++ ++If configure is called without --enable-tcg-interpreter, it will ++suggest using this option. Setting it automatically would need ++additional code in configure which must be fixed when new native TCG ++implementations are added. ++ ++System emulation should work on any 32 or 64 bit host. ++User mode emulation might work. Maybe a new linker script (*.ld) ++is needed. Byte order might be wrong (on big endian hosts) ++and need fixes in configure. ++ ++For hosts with native TCG, the interpreter TCI can be enabled by ++ ++ configure --enable-tcg-interpreter ++ ++The only difference from running QEMU with TCI to running without TCI ++should be speed. Especially during development of TCI, it was very ++useful to compare runs with and without TCI. Create /tmp/qemu.log by ++ ++ qemu-system-i386 -d in_asm,op_opt,cpu -D /tmp/qemu.log -singlestep ++ ++once with interpreter and once without interpreter and compare the resulting ++qemu.log files. This is also useful to see the effects of additional ++registers or additional opcodes (it is easy to modify the virtual machine). ++It can also be used to verify native TCGs. ++ ++Hosts with native TCG can also enable TCI by claiming to be unsupported: ++ ++ configure --cpu=unknown --enable-tcg-interpreter ++ ++configure then no longer uses the native linker script (*.ld) for ++user mode emulation. ++ ++ ++4) Status ++ ++TCI needs special implementation for 32 and 64 bit host, 32 and 64 bit target, ++host and target with same or different endianness. ++ ++ | host (le) host (be) ++ | 32 64 32 64 ++------------+------------------------------------------------------------ ++target (le) | s0, u0 s1, u1 s?, u? s?, u? ++32 bit | ++ | ++target (le) | sc, uc s1, u1 s?, u? s?, u? ++64 bit | ++ | ++target (be) | sc, u0 sc, uc s?, u? s?, u? ++32 bit | ++ | ++target (be) | sc, uc sc, uc s?, u? s?, u? ++64 bit | ++ | ++ ++System emulation ++s? = untested ++sc = compiles ++s0 = bios works ++s1 = grub works ++s2 = Linux boots ++ ++Linux user mode emulation ++u? = untested ++uc = compiles ++u0 = static hello works ++u1 = linux-user-test works ++ ++5) Todo list ++ ++* TCI is not widely tested. It was written and tested on a x86_64 host ++ running i386 and x86_64 system emulation and Linux user mode. ++ A cross compiled QEMU for i386 host also works with the same basic tests. ++ A cross compiled QEMU for mipsel host works, too. It is terribly slow ++ because I run it in a mips malta emulation, so it is an interpreted ++ emulation in an emulation. ++ A cross compiled QEMU for arm host works (tested with pc bios). ++ A cross compiled QEMU for ppc host works at least partially: ++ i386-linux-user/qemu-i386 can run a simple hello-world program ++ (tested in a ppc emulation). ++ ++* Some TCG opcodes are either missing in the code generator and/or ++ in the interpreter. These opcodes raise a runtime exception, so it is ++ possible to see where code must be added. ++ ++* The pseudo code is not optimized and still ugly. For hosts with special ++ alignment requirements, it needs some fixes (maybe aligned bytecode ++ would also improve speed for hosts which support byte alignment). ++ ++* A better disassembler for the pseudo code would be nice (a very primitive ++ disassembler is included in tcg-target.inc.c). ++ ++* It might be useful to have a runtime option which selects the native TCG ++ or TCI, so QEMU would have to include two TCGs. Today, selecting TCI ++ is a configure option, so you need two compilations of QEMU. +diff --git a/qemu/tcg/tci/tcg-target.h b/qemu/tcg/tci/tcg-target.h +new file mode 100644 +index 0000000..93cbaef +--- /dev/null ++++ b/qemu/tcg/tci/tcg-target.h +@@ -0,0 +1,221 @@ ++/* ++ * Tiny Code Generator for QEMU ++ * ++ * Copyright (c) 2009, 2011 Stefan Weil ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the "Software"), to deal ++ * in the Software without restriction, including without limitation the rights ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++/* ++ * This code implements a TCG which does not generate machine code for some ++ * real target machine but which generates virtual machine code for an ++ * interpreter. Interpreted pseudo code is slow, but it works on any host. ++ * ++ * Some remarks might help in understanding the code: ++ * ++ * "target" or "TCG target" is the machine which runs the generated code. ++ * This is different to the usual meaning in QEMU where "target" is the ++ * emulated machine. So normally QEMU host is identical to TCG target. ++ * Here the TCG target is a virtual machine, but this virtual machine must ++ * use the same word size like the real machine. ++ * Therefore, we need both 32 and 64 bit virtual machines (interpreter). ++ */ ++ ++#ifndef TCG_TARGET_H ++#define TCG_TARGET_H ++ ++#define TCG_TARGET_INTERPRETER 1 ++#define TCG_TARGET_INSN_UNIT_SIZE 1 ++#define TCG_TARGET_TLB_DISPLACEMENT_BITS 32 ++ ++#ifdef __EMSCRIPTEN__ ++/* ++ * wasm32 has 32-bit pointers, but qemu 5.0's 32-bit TCI path is incomplete ++ * (TODO() stubs for i64 stores etc.). TCI's register file is virtual state, ++ * so run the interpreter with 64-bit registers regardless of pointer size; ++ * guest addresses fit in u64 and helper calls marshal through trampolines. ++ */ ++# define TCG_TARGET_REG_BITS 64 ++#elif UINTPTR_MAX == UINT32_MAX ++# define TCG_TARGET_REG_BITS 32 ++#elif UINTPTR_MAX == UINT64_MAX ++# define TCG_TARGET_REG_BITS 64 ++#else ++# error Unknown pointer size for tci target ++#endif ++ ++#ifdef CONFIG_DEBUG_TCG ++/* Enable debug output. */ ++#define CONFIG_DEBUG_TCG_INTERPRETER ++#endif ++ ++/* Optional instructions. */ ++ ++#define TCG_TARGET_HAS_bswap16_i32 1 ++#define TCG_TARGET_HAS_bswap32_i32 1 ++#define TCG_TARGET_HAS_div_i32 1 ++#define TCG_TARGET_HAS_rem_i32 1 ++#define TCG_TARGET_HAS_ext8s_i32 1 ++#define TCG_TARGET_HAS_ext16s_i32 1 ++#define TCG_TARGET_HAS_ext8u_i32 1 ++#define TCG_TARGET_HAS_ext16u_i32 1 ++#define TCG_TARGET_HAS_andc_i32 0 ++#define TCG_TARGET_HAS_deposit_i32 1 ++#define TCG_TARGET_HAS_extract_i32 0 ++#define TCG_TARGET_HAS_sextract_i32 0 ++#define TCG_TARGET_HAS_extract2_i32 0 ++#define TCG_TARGET_HAS_eqv_i32 0 ++#define TCG_TARGET_HAS_nand_i32 0 ++#define TCG_TARGET_HAS_nor_i32 0 ++#define TCG_TARGET_HAS_clz_i32 0 ++#define TCG_TARGET_HAS_ctz_i32 0 ++#define TCG_TARGET_HAS_ctpop_i32 0 ++#define TCG_TARGET_HAS_neg_i32 1 ++#define TCG_TARGET_HAS_not_i32 1 ++#define TCG_TARGET_HAS_orc_i32 0 ++#define TCG_TARGET_HAS_rot_i32 1 ++#define TCG_TARGET_HAS_movcond_i32 0 ++#define TCG_TARGET_HAS_muls2_i32 0 ++#define TCG_TARGET_HAS_muluh_i32 0 ++#define TCG_TARGET_HAS_mulsh_i32 0 ++#define TCG_TARGET_HAS_goto_ptr 0 ++#define TCG_TARGET_HAS_direct_jump 1 ++ ++#if TCG_TARGET_REG_BITS == 64 ++#define TCG_TARGET_HAS_extrl_i64_i32 0 ++#define TCG_TARGET_HAS_extrh_i64_i32 0 ++#define TCG_TARGET_HAS_bswap16_i64 1 ++#define TCG_TARGET_HAS_bswap32_i64 1 ++#define TCG_TARGET_HAS_bswap64_i64 1 ++#define TCG_TARGET_HAS_deposit_i64 1 ++#define TCG_TARGET_HAS_extract_i64 0 ++#define TCG_TARGET_HAS_sextract_i64 0 ++#define TCG_TARGET_HAS_extract2_i64 0 ++#define TCG_TARGET_HAS_div_i64 0 ++#define TCG_TARGET_HAS_rem_i64 0 ++#define TCG_TARGET_HAS_ext8s_i64 1 ++#define TCG_TARGET_HAS_ext16s_i64 1 ++#define TCG_TARGET_HAS_ext32s_i64 1 ++#define TCG_TARGET_HAS_ext8u_i64 1 ++#define TCG_TARGET_HAS_ext16u_i64 1 ++#define TCG_TARGET_HAS_ext32u_i64 1 ++#define TCG_TARGET_HAS_andc_i64 0 ++#define TCG_TARGET_HAS_eqv_i64 0 ++#define TCG_TARGET_HAS_nand_i64 0 ++#define TCG_TARGET_HAS_nor_i64 0 ++#define TCG_TARGET_HAS_clz_i64 0 ++#define TCG_TARGET_HAS_ctz_i64 0 ++#define TCG_TARGET_HAS_ctpop_i64 0 ++#define TCG_TARGET_HAS_neg_i64 1 ++#define TCG_TARGET_HAS_not_i64 1 ++#define TCG_TARGET_HAS_orc_i64 0 ++#define TCG_TARGET_HAS_rot_i64 1 ++#define TCG_TARGET_HAS_movcond_i64 0 ++#define TCG_TARGET_HAS_muls2_i64 0 ++#define TCG_TARGET_HAS_add2_i32 0 ++#define TCG_TARGET_HAS_sub2_i32 0 ++#define TCG_TARGET_HAS_mulu2_i32 0 ++#define TCG_TARGET_HAS_add2_i64 0 ++#define TCG_TARGET_HAS_sub2_i64 0 ++#define TCG_TARGET_HAS_mulu2_i64 0 ++#define TCG_TARGET_HAS_muluh_i64 0 ++#define TCG_TARGET_HAS_mulsh_i64 0 ++#else ++#define TCG_TARGET_HAS_mulu2_i32 1 ++#endif /* TCG_TARGET_REG_BITS == 64 */ ++ ++/* Number of registers available. ++ For 32 bit hosts, we need more than 8 registers (call arguments). */ ++/* #define TCG_TARGET_NB_REGS 8 */ ++#define TCG_TARGET_NB_REGS 16 ++/* #define TCG_TARGET_NB_REGS 32 */ ++ ++/* List of registers which are used by TCG. */ ++typedef enum { ++ TCG_REG_R0 = 0, ++ TCG_REG_R1, ++ TCG_REG_R2, ++ TCG_REG_R3, ++ TCG_REG_R4, ++ TCG_REG_R5, ++ TCG_REG_R6, ++ TCG_REG_R7, ++#if TCG_TARGET_NB_REGS >= 16 ++ TCG_REG_R8, ++ TCG_REG_R9, ++ TCG_REG_R10, ++ TCG_REG_R11, ++ TCG_REG_R12, ++ TCG_REG_R13, ++ TCG_REG_R14, ++ TCG_REG_R15, ++#if TCG_TARGET_NB_REGS >= 32 ++ TCG_REG_R16, ++ TCG_REG_R17, ++ TCG_REG_R18, ++ TCG_REG_R19, ++ TCG_REG_R20, ++ TCG_REG_R21, ++ TCG_REG_R22, ++ TCG_REG_R23, ++ TCG_REG_R24, ++ TCG_REG_R25, ++ TCG_REG_R26, ++ TCG_REG_R27, ++ TCG_REG_R28, ++ TCG_REG_R29, ++ TCG_REG_R30, ++ TCG_REG_R31, ++#endif ++#endif ++ /* Special value UINT8_MAX is used by TCI to encode constant values. */ ++ TCG_CONST = UINT8_MAX ++} TCGReg; ++ ++#define TCG_AREG0 (TCG_TARGET_NB_REGS - 2) ++ ++/* Used for function call generation. */ ++#define TCG_REG_CALL_STACK (TCG_TARGET_NB_REGS - 1) ++#define TCG_TARGET_CALL_STACK_OFFSET 0 ++#define TCG_TARGET_STACK_ALIGN 16 ++ ++void tci_disas(uint8_t opc); ++ ++#define HAVE_TCG_QEMU_TB_EXEC ++ ++static inline void flush_icache_range(uintptr_t start, uintptr_t stop) ++{ ++} ++ ++/* We could notice __i386__ or __s390x__ and reduce the barriers depending ++ on the host. But if you want performance, you use the normal backend. ++ We prefer consistency across hosts on this. */ ++#define TCG_TARGET_DEFAULT_MO (0) ++ ++#define TCG_TARGET_HAS_MEMORY_BSWAP 1 ++ ++static inline void tb_target_set_jmp_target(uintptr_t tc_ptr, ++ uintptr_t jmp_addr, uintptr_t addr) ++{ ++ /* patch the branch destination */ ++ atomic_set((int32_t *)jmp_addr, addr - (jmp_addr + 4)); ++ /* no need to flush icache explicitly */ ++} ++ ++#endif /* TCG_TARGET_H */ +diff --git a/qemu/tcg/tci/tcg-target.inc.c b/qemu/tcg/tci/tcg-target.inc.c +new file mode 100644 +index 0000000..4d6fe6a +--- /dev/null ++++ b/qemu/tcg/tci/tcg-target.inc.c +@@ -0,0 +1,897 @@ ++/* ++ * Tiny Code Generator for QEMU ++ * ++ * Copyright (c) 2009, 2011 Stefan Weil ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the "Software"), to deal ++ * in the Software without restriction, including without limitation the rights ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++/* TODO list: ++ * - See TODO comments in code. ++ */ ++ ++/* Marker for missing code. */ ++#define TODO() \ ++ do { \ ++ fprintf(stderr, "TODO %s:%u: %s()\n", \ ++ __FILE__, __LINE__, __func__); \ ++ tcg_abort(); \ ++ } while (0) ++ ++/* Bitfield n...m (in 32 bit value). */ ++#define BITS(n, m) (((0xffffffffU << (31 - n)) >> (31 - n + m)) << m) ++ ++/* Macros used in tcg_target_op_defs. */ ++#define R "r" ++#define RI "ri" ++#if TCG_TARGET_REG_BITS == 32 ++# define R64 "r", "r" ++#else ++# define R64 "r" ++#endif ++#if TARGET_LONG_BITS > TCG_TARGET_REG_BITS ++# define L "L", "L" ++# define S "S", "S" ++#else ++# define L "L" ++# define S "S" ++#endif ++ ++/* TODO: documentation. */ ++static const TCGTargetOpDef tcg_target_op_defs[] = { ++ { INDEX_op_exit_tb, { NULL } }, ++ { INDEX_op_goto_tb, { NULL } }, ++ { INDEX_op_br, { NULL } }, ++ ++ { INDEX_op_ld8u_i32, { R, R } }, ++ { INDEX_op_ld8s_i32, { R, R } }, ++ { INDEX_op_ld16u_i32, { R, R } }, ++ { INDEX_op_ld16s_i32, { R, R } }, ++ { INDEX_op_ld_i32, { R, R } }, ++ { INDEX_op_st8_i32, { R, R } }, ++ { INDEX_op_st16_i32, { R, R } }, ++ { INDEX_op_st_i32, { R, R } }, ++ ++ { INDEX_op_add_i32, { R, RI, RI } }, ++ { INDEX_op_sub_i32, { R, RI, RI } }, ++ { INDEX_op_mul_i32, { R, RI, RI } }, ++#if TCG_TARGET_HAS_div_i32 ++ { INDEX_op_div_i32, { R, R, R } }, ++ { INDEX_op_divu_i32, { R, R, R } }, ++ { INDEX_op_rem_i32, { R, R, R } }, ++ { INDEX_op_remu_i32, { R, R, R } }, ++#elif TCG_TARGET_HAS_div2_i32 ++ { INDEX_op_div2_i32, { R, R, "0", "1", R } }, ++ { INDEX_op_divu2_i32, { R, R, "0", "1", R } }, ++#endif ++ /* TODO: Does R, RI, RI result in faster code than R, R, RI? ++ If both operands are constants, we can optimize. */ ++ { INDEX_op_and_i32, { R, RI, RI } }, ++#if TCG_TARGET_HAS_andc_i32 ++ { INDEX_op_andc_i32, { R, RI, RI } }, ++#endif ++#if TCG_TARGET_HAS_eqv_i32 ++ { INDEX_op_eqv_i32, { R, RI, RI } }, ++#endif ++#if TCG_TARGET_HAS_nand_i32 ++ { INDEX_op_nand_i32, { R, RI, RI } }, ++#endif ++#if TCG_TARGET_HAS_nor_i32 ++ { INDEX_op_nor_i32, { R, RI, RI } }, ++#endif ++ { INDEX_op_or_i32, { R, RI, RI } }, ++#if TCG_TARGET_HAS_orc_i32 ++ { INDEX_op_orc_i32, { R, RI, RI } }, ++#endif ++ { INDEX_op_xor_i32, { R, RI, RI } }, ++ { INDEX_op_shl_i32, { R, RI, RI } }, ++ { INDEX_op_shr_i32, { R, RI, RI } }, ++ { INDEX_op_sar_i32, { R, RI, RI } }, ++#if TCG_TARGET_HAS_rot_i32 ++ { INDEX_op_rotl_i32, { R, RI, RI } }, ++ { INDEX_op_rotr_i32, { R, RI, RI } }, ++#endif ++#if TCG_TARGET_HAS_deposit_i32 ++ { INDEX_op_deposit_i32, { R, "0", R } }, ++#endif ++ ++ { INDEX_op_brcond_i32, { R, RI } }, ++ ++ { INDEX_op_setcond_i32, { R, R, RI } }, ++#if TCG_TARGET_REG_BITS == 64 ++ { INDEX_op_setcond_i64, { R, R, RI } }, ++#endif /* TCG_TARGET_REG_BITS == 64 */ ++ ++#if TCG_TARGET_REG_BITS == 32 ++ /* TODO: Support R, R, R, R, RI, RI? Will it be faster? */ ++ { INDEX_op_add2_i32, { R, R, R, R, R, R } }, ++ { INDEX_op_sub2_i32, { R, R, R, R, R, R } }, ++ { INDEX_op_brcond2_i32, { R, R, RI, RI } }, ++ { INDEX_op_mulu2_i32, { R, R, R, R } }, ++ { INDEX_op_setcond2_i32, { R, R, R, RI, RI } }, ++#endif ++ ++#if TCG_TARGET_HAS_not_i32 ++ { INDEX_op_not_i32, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_neg_i32 ++ { INDEX_op_neg_i32, { R, R } }, ++#endif ++ ++#if TCG_TARGET_REG_BITS == 64 ++ { INDEX_op_ld8u_i64, { R, R } }, ++ { INDEX_op_ld8s_i64, { R, R } }, ++ { INDEX_op_ld16u_i64, { R, R } }, ++ { INDEX_op_ld16s_i64, { R, R } }, ++ { INDEX_op_ld32u_i64, { R, R } }, ++ { INDEX_op_ld32s_i64, { R, R } }, ++ { INDEX_op_ld_i64, { R, R } }, ++ ++ { INDEX_op_st8_i64, { R, R } }, ++ { INDEX_op_st16_i64, { R, R } }, ++ { INDEX_op_st32_i64, { R, R } }, ++ { INDEX_op_st_i64, { R, R } }, ++ ++ { INDEX_op_add_i64, { R, RI, RI } }, ++ { INDEX_op_sub_i64, { R, RI, RI } }, ++ { INDEX_op_mul_i64, { R, RI, RI } }, ++#if TCG_TARGET_HAS_div_i64 ++ { INDEX_op_div_i64, { R, R, R } }, ++ { INDEX_op_divu_i64, { R, R, R } }, ++ { INDEX_op_rem_i64, { R, R, R } }, ++ { INDEX_op_remu_i64, { R, R, R } }, ++#elif TCG_TARGET_HAS_div2_i64 ++ { INDEX_op_div2_i64, { R, R, "0", "1", R } }, ++ { INDEX_op_divu2_i64, { R, R, "0", "1", R } }, ++#endif ++ { INDEX_op_and_i64, { R, RI, RI } }, ++#if TCG_TARGET_HAS_andc_i64 ++ { INDEX_op_andc_i64, { R, RI, RI } }, ++#endif ++#if TCG_TARGET_HAS_eqv_i64 ++ { INDEX_op_eqv_i64, { R, RI, RI } }, ++#endif ++#if TCG_TARGET_HAS_nand_i64 ++ { INDEX_op_nand_i64, { R, RI, RI } }, ++#endif ++#if TCG_TARGET_HAS_nor_i64 ++ { INDEX_op_nor_i64, { R, RI, RI } }, ++#endif ++ { INDEX_op_or_i64, { R, RI, RI } }, ++#if TCG_TARGET_HAS_orc_i64 ++ { INDEX_op_orc_i64, { R, RI, RI } }, ++#endif ++ { INDEX_op_xor_i64, { R, RI, RI } }, ++ { INDEX_op_shl_i64, { R, RI, RI } }, ++ { INDEX_op_shr_i64, { R, RI, RI } }, ++ { INDEX_op_sar_i64, { R, RI, RI } }, ++#if TCG_TARGET_HAS_rot_i64 ++ { INDEX_op_rotl_i64, { R, RI, RI } }, ++ { INDEX_op_rotr_i64, { R, RI, RI } }, ++#endif ++#if TCG_TARGET_HAS_deposit_i64 ++ { INDEX_op_deposit_i64, { R, "0", R } }, ++#endif ++ { INDEX_op_brcond_i64, { R, RI } }, ++ ++#if TCG_TARGET_HAS_ext8s_i64 ++ { INDEX_op_ext8s_i64, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_ext16s_i64 ++ { INDEX_op_ext16s_i64, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_ext32s_i64 ++ { INDEX_op_ext32s_i64, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_ext8u_i64 ++ { INDEX_op_ext8u_i64, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_ext16u_i64 ++ { INDEX_op_ext16u_i64, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_ext32u_i64 ++ { INDEX_op_ext32u_i64, { R, R } }, ++#endif ++ { INDEX_op_ext_i32_i64, { R, R } }, ++ { INDEX_op_extu_i32_i64, { R, R } }, ++#if TCG_TARGET_HAS_bswap16_i64 ++ { INDEX_op_bswap16_i64, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_bswap32_i64 ++ { INDEX_op_bswap32_i64, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_bswap64_i64 ++ { INDEX_op_bswap64_i64, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_not_i64 ++ { INDEX_op_not_i64, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_neg_i64 ++ { INDEX_op_neg_i64, { R, R } }, ++#endif ++#endif /* TCG_TARGET_REG_BITS == 64 */ ++ ++ { INDEX_op_qemu_ld_i32, { R, L } }, ++ { INDEX_op_qemu_ld_i64, { R64, L } }, ++ ++ { INDEX_op_qemu_st_i32, { R, S } }, ++ { INDEX_op_qemu_st_i64, { R64, S } }, ++ ++#if TCG_TARGET_HAS_ext8s_i32 ++ { INDEX_op_ext8s_i32, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_ext16s_i32 ++ { INDEX_op_ext16s_i32, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_ext8u_i32 ++ { INDEX_op_ext8u_i32, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_ext16u_i32 ++ { INDEX_op_ext16u_i32, { R, R } }, ++#endif ++ ++#if TCG_TARGET_HAS_bswap16_i32 ++ { INDEX_op_bswap16_i32, { R, R } }, ++#endif ++#if TCG_TARGET_HAS_bswap32_i32 ++ { INDEX_op_bswap32_i32, { R, R } }, ++#endif ++ ++ { INDEX_op_mb, { } }, ++ { -1 }, ++}; ++ ++static const TCGTargetOpDef *tcg_target_op_def(TCGOpcode op) ++{ ++ int i, n = ARRAY_SIZE(tcg_target_op_defs); ++ ++ for (i = 0; i < n; ++i) { ++ if (tcg_target_op_defs[i].op == op) { ++ return &tcg_target_op_defs[i]; ++ } ++ } ++ return NULL; ++} ++ ++static const int tcg_target_reg_alloc_order[] = { ++ TCG_REG_R0, ++ TCG_REG_R1, ++ TCG_REG_R2, ++ TCG_REG_R3, ++#if 0 /* used for TCG_REG_CALL_STACK */ ++ TCG_REG_R4, ++#endif ++ TCG_REG_R5, ++ TCG_REG_R6, ++ TCG_REG_R7, ++#if TCG_TARGET_NB_REGS >= 16 ++ TCG_REG_R8, ++ TCG_REG_R9, ++ TCG_REG_R10, ++ TCG_REG_R11, ++ TCG_REG_R12, ++ TCG_REG_R13, ++ TCG_REG_R14, ++ TCG_REG_R15, ++#endif ++}; ++ ++#if MAX_OPC_PARAM_IARGS != 6 ++# error Fix needed, number of supported input arguments changed! ++#endif ++ ++static const int tcg_target_call_iarg_regs[] = { ++ TCG_REG_R0, ++ TCG_REG_R1, ++ TCG_REG_R2, ++ TCG_REG_R3, ++#if 0 /* used for TCG_REG_CALL_STACK */ ++ TCG_REG_R4, ++#endif ++ TCG_REG_R5, ++ TCG_REG_R6, ++#if TCG_TARGET_REG_BITS == 32 ++ /* 32 bit hosts need 2 * MAX_OPC_PARAM_IARGS registers. */ ++ TCG_REG_R7, ++#if TCG_TARGET_NB_REGS >= 16 ++ TCG_REG_R8, ++ TCG_REG_R9, ++ TCG_REG_R10, ++ TCG_REG_R11, ++ TCG_REG_R12, ++#else ++# error Too few input registers available ++#endif ++#endif ++}; ++ ++static const int tcg_target_call_oarg_regs[] = { ++ TCG_REG_R0, ++#if TCG_TARGET_REG_BITS == 32 ++ TCG_REG_R1 ++#endif ++}; ++ ++#ifdef CONFIG_DEBUG_TCG ++static const char *const tcg_target_reg_names[TCG_TARGET_NB_REGS] = { ++ "r00", ++ "r01", ++ "r02", ++ "r03", ++ "r04", ++ "r05", ++ "r06", ++ "r07", ++#if TCG_TARGET_NB_REGS >= 16 ++ "r08", ++ "r09", ++ "r10", ++ "r11", ++ "r12", ++ "r13", ++ "r14", ++ "r15", ++#if TCG_TARGET_NB_REGS >= 32 ++ "r16", ++ "r17", ++ "r18", ++ "r19", ++ "r20", ++ "r21", ++ "r22", ++ "r23", ++ "r24", ++ "r25", ++ "r26", ++ "r27", ++ "r28", ++ "r29", ++ "r30", ++ "r31" ++#endif ++#endif ++}; ++#endif ++ ++static bool patch_reloc(tcg_insn_unit *code_ptr, int type, ++ intptr_t value, intptr_t addend) ++{ ++ /* tcg_out_reloc always uses the same type, addend. */ ++ tcg_debug_assert(type == sizeof(tcg_target_long)); ++ tcg_debug_assert(addend == 0); ++ tcg_debug_assert(value != 0); ++ if (TCG_TARGET_REG_BITS == 32) { ++ tcg_patch32(code_ptr, value); ++ } else { ++ tcg_patch64(code_ptr, value); ++ } ++ return true; ++} ++ ++/* Parse target specific constraints. */ ++static const char *target_parse_constraint(TCGArgConstraint *ct, ++ const char *ct_str, TCGType type) ++{ ++ switch (*ct_str++) { ++ case 'r': ++ case 'L': /* qemu_ld constraint */ ++ case 'S': /* qemu_st constraint */ ++ ct->ct |= TCG_CT_REG; ++ ct->u.regs = BIT(TCG_TARGET_NB_REGS) - 1; ++ break; ++ default: ++ return NULL; ++ } ++ return ct_str; ++} ++ ++#if defined(CONFIG_DEBUG_TCG_INTERPRETER) ++/* Show current bytecode. Used by tcg interpreter. */ ++void tci_disas(uint8_t opc) ++{ ++ const TCGOpDef *def = &tcg_op_defs[opc]; ++ fprintf(stderr, "TCG %s %u, %u, %u\n", ++ def->name, def->nb_oargs, def->nb_iargs, def->nb_cargs); ++} ++#endif ++ ++/* Write value (native size). */ ++static void tcg_out_i(TCGContext *s, tcg_target_ulong v) ++{ ++ if (TCG_TARGET_REG_BITS == 32) { ++ tcg_out32(s, v); ++ } else { ++ tcg_out64(s, v); ++ } ++} ++ ++/* Write opcode. */ ++static void tcg_out_op_t(TCGContext *s, TCGOpcode op) ++{ ++ tcg_out8(s, op); ++ tcg_out8(s, 0); ++} ++ ++/* Write register. */ ++static void tcg_out_r(TCGContext *s, TCGArg t0) ++{ ++ tcg_debug_assert(t0 < TCG_TARGET_NB_REGS); ++ tcg_out8(s, t0); ++} ++ ++/* Write register or constant (native size). */ ++static void tcg_out_ri(TCGContext *s, int const_arg, TCGArg arg) ++{ ++ if (const_arg) { ++ tcg_debug_assert(const_arg == 1); ++ tcg_out8(s, TCG_CONST); ++ tcg_out_i(s, arg); ++ } else { ++ tcg_out_r(s, arg); ++ } ++} ++ ++/* Write register or constant (32 bit). */ ++static void tcg_out_ri32(TCGContext *s, int const_arg, TCGArg arg) ++{ ++ if (const_arg) { ++ tcg_debug_assert(const_arg == 1); ++ tcg_out8(s, TCG_CONST); ++ tcg_out32(s, arg); ++ } else { ++ tcg_out_r(s, arg); ++ } ++} ++ ++#if TCG_TARGET_REG_BITS == 64 ++/* Write register or constant (64 bit). */ ++static void tcg_out_ri64(TCGContext *s, int const_arg, TCGArg arg) ++{ ++ if (const_arg) { ++ tcg_debug_assert(const_arg == 1); ++ tcg_out8(s, TCG_CONST); ++ tcg_out64(s, arg); ++ } else { ++ tcg_out_r(s, arg); ++ } ++} ++#endif ++ ++/* Write label. */ ++static void tci_out_label(TCGContext *s, TCGLabel *label) ++{ ++ if (label->has_value) { ++ tcg_out_i(s, label->u.value); ++ tcg_debug_assert(label->u.value); ++ } else { ++ tcg_out_reloc(s, s->code_ptr, sizeof(tcg_target_ulong), label, 0); ++ s->code_ptr += sizeof(tcg_target_ulong); ++ } ++} ++ ++static void tcg_out_ld(TCGContext *s, TCGType type, TCGReg ret, TCGReg arg1, ++ intptr_t arg2) ++{ ++ uint8_t *old_code_ptr = s->code_ptr; ++ if (type == TCG_TYPE_I32) { ++ tcg_out_op_t(s, INDEX_op_ld_i32); ++ tcg_out_r(s, ret); ++ tcg_out_r(s, arg1); ++ tcg_out32(s, arg2); ++ } else { ++ tcg_debug_assert(type == TCG_TYPE_I64); ++#if TCG_TARGET_REG_BITS == 64 ++ tcg_out_op_t(s, INDEX_op_ld_i64); ++ tcg_out_r(s, ret); ++ tcg_out_r(s, arg1); ++ tcg_debug_assert(arg2 == (int32_t)arg2); ++ tcg_out32(s, arg2); ++#else ++ TODO(); ++#endif ++ } ++ old_code_ptr[1] = s->code_ptr - old_code_ptr; ++} ++ ++static bool tcg_out_mov(TCGContext *s, TCGType type, TCGReg ret, TCGReg arg) ++{ ++ uint8_t *old_code_ptr = s->code_ptr; ++ tcg_debug_assert(ret != arg); ++#if TCG_TARGET_REG_BITS == 32 ++ tcg_out_op_t(s, INDEX_op_mov_i32); ++#else ++ tcg_out_op_t(s, INDEX_op_mov_i64); ++#endif ++ tcg_out_r(s, ret); ++ tcg_out_r(s, arg); ++ old_code_ptr[1] = s->code_ptr - old_code_ptr; ++ return true; ++} ++ ++static void tcg_out_movi(TCGContext *s, TCGType type, ++ TCGReg t0, tcg_target_long arg) ++{ ++ uint8_t *old_code_ptr = s->code_ptr; ++ uint32_t arg32 = arg; ++ if (type == TCG_TYPE_I32 || arg == arg32) { ++ tcg_out_op_t(s, INDEX_op_movi_i32); ++ tcg_out_r(s, t0); ++ tcg_out32(s, arg32); ++ } else { ++ tcg_debug_assert(type == TCG_TYPE_I64); ++#if TCG_TARGET_REG_BITS == 64 ++ tcg_out_op_t(s, INDEX_op_movi_i64); ++ tcg_out_r(s, t0); ++ tcg_out64(s, arg); ++#else ++ TODO(); ++#endif ++ } ++ old_code_ptr[1] = s->code_ptr - old_code_ptr; ++} ++ ++static inline void tcg_out_call(TCGContext *s, tcg_insn_unit *arg) ++{ ++ uint8_t *old_code_ptr = s->code_ptr; ++ tcg_out_op_t(s, INDEX_op_call); ++ tcg_out_ri(s, 1, (uintptr_t)arg); ++ old_code_ptr[1] = s->code_ptr - old_code_ptr; ++} ++ ++static void tcg_out_op(TCGContext *s, TCGOpcode opc, const TCGArg *args, ++ const int *const_args) ++{ ++ uint8_t *old_code_ptr = s->code_ptr; ++ ++ tcg_out_op_t(s, opc); ++ ++ switch (opc) { ++ case INDEX_op_exit_tb: ++ tcg_out64(s, args[0]); ++ break; ++ case INDEX_op_goto_tb: ++ if (s->tb_jmp_insn_offset) { ++ /* Direct jump method. */ ++ /* Align for atomic patching and thread safety */ ++ s->code_ptr = QEMU_ALIGN_PTR_UP(s->code_ptr, 4); ++ s->tb_jmp_insn_offset[args[0]] = tcg_current_code_size(s); ++ tcg_out32(s, 0); ++ } else { ++ /* Indirect jump method. */ ++ TODO(); ++ } ++ set_jmp_reset_offset(s, args[0]); ++ break; ++ case INDEX_op_br: ++ tci_out_label(s, arg_label(args[0])); ++ break; ++ case INDEX_op_setcond_i32: ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ tcg_out_ri32(s, const_args[2], args[2]); ++ tcg_out8(s, args[3]); /* condition */ ++ break; ++#if TCG_TARGET_REG_BITS == 32 ++ case INDEX_op_setcond2_i32: ++ /* setcond2_i32 cond, t0, t1_low, t1_high, t2_low, t2_high */ ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ tcg_out_r(s, args[2]); ++ tcg_out_ri32(s, const_args[3], args[3]); ++ tcg_out_ri32(s, const_args[4], args[4]); ++ tcg_out8(s, args[5]); /* condition */ ++ break; ++#elif TCG_TARGET_REG_BITS == 64 ++ case INDEX_op_setcond_i64: ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ tcg_out_ri64(s, const_args[2], args[2]); ++ tcg_out8(s, args[3]); /* condition */ ++ break; ++#endif ++ case INDEX_op_ld8u_i32: ++ case INDEX_op_ld8s_i32: ++ case INDEX_op_ld16u_i32: ++ case INDEX_op_ld16s_i32: ++ case INDEX_op_ld_i32: ++ case INDEX_op_st8_i32: ++ case INDEX_op_st16_i32: ++ case INDEX_op_st_i32: ++ case INDEX_op_ld8u_i64: ++ case INDEX_op_ld8s_i64: ++ case INDEX_op_ld16u_i64: ++ case INDEX_op_ld16s_i64: ++ case INDEX_op_ld32u_i64: ++ case INDEX_op_ld32s_i64: ++ case INDEX_op_ld_i64: ++ case INDEX_op_st8_i64: ++ case INDEX_op_st16_i64: ++ case INDEX_op_st32_i64: ++ case INDEX_op_st_i64: ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ tcg_debug_assert(args[2] == (int32_t)args[2]); ++ tcg_out32(s, args[2]); ++ break; ++ case INDEX_op_add_i32: ++ case INDEX_op_sub_i32: ++ case INDEX_op_mul_i32: ++ case INDEX_op_and_i32: ++ case INDEX_op_andc_i32: /* Optional (TCG_TARGET_HAS_andc_i32). */ ++ case INDEX_op_eqv_i32: /* Optional (TCG_TARGET_HAS_eqv_i32). */ ++ case INDEX_op_nand_i32: /* Optional (TCG_TARGET_HAS_nand_i32). */ ++ case INDEX_op_nor_i32: /* Optional (TCG_TARGET_HAS_nor_i32). */ ++ case INDEX_op_or_i32: ++ case INDEX_op_orc_i32: /* Optional (TCG_TARGET_HAS_orc_i32). */ ++ case INDEX_op_xor_i32: ++ case INDEX_op_shl_i32: ++ case INDEX_op_shr_i32: ++ case INDEX_op_sar_i32: ++ case INDEX_op_rotl_i32: /* Optional (TCG_TARGET_HAS_rot_i32). */ ++ case INDEX_op_rotr_i32: /* Optional (TCG_TARGET_HAS_rot_i32). */ ++ tcg_out_r(s, args[0]); ++ tcg_out_ri32(s, const_args[1], args[1]); ++ tcg_out_ri32(s, const_args[2], args[2]); ++ break; ++ case INDEX_op_deposit_i32: /* Optional (TCG_TARGET_HAS_deposit_i32). */ ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ tcg_out_r(s, args[2]); ++ tcg_debug_assert(args[3] <= UINT8_MAX); ++ tcg_out8(s, args[3]); ++ tcg_debug_assert(args[4] <= UINT8_MAX); ++ tcg_out8(s, args[4]); ++ break; ++ ++#if TCG_TARGET_REG_BITS == 64 ++ case INDEX_op_add_i64: ++ case INDEX_op_sub_i64: ++ case INDEX_op_mul_i64: ++ case INDEX_op_and_i64: ++ case INDEX_op_andc_i64: /* Optional (TCG_TARGET_HAS_andc_i64). */ ++ case INDEX_op_eqv_i64: /* Optional (TCG_TARGET_HAS_eqv_i64). */ ++ case INDEX_op_nand_i64: /* Optional (TCG_TARGET_HAS_nand_i64). */ ++ case INDEX_op_nor_i64: /* Optional (TCG_TARGET_HAS_nor_i64). */ ++ case INDEX_op_or_i64: ++ case INDEX_op_orc_i64: /* Optional (TCG_TARGET_HAS_orc_i64). */ ++ case INDEX_op_xor_i64: ++ case INDEX_op_shl_i64: ++ case INDEX_op_shr_i64: ++ case INDEX_op_sar_i64: ++ case INDEX_op_rotl_i64: /* Optional (TCG_TARGET_HAS_rot_i64). */ ++ case INDEX_op_rotr_i64: /* Optional (TCG_TARGET_HAS_rot_i64). */ ++ tcg_out_r(s, args[0]); ++ tcg_out_ri64(s, const_args[1], args[1]); ++ tcg_out_ri64(s, const_args[2], args[2]); ++ break; ++ case INDEX_op_deposit_i64: /* Optional (TCG_TARGET_HAS_deposit_i64). */ ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ tcg_out_r(s, args[2]); ++ tcg_debug_assert(args[3] <= UINT8_MAX); ++ tcg_out8(s, args[3]); ++ tcg_debug_assert(args[4] <= UINT8_MAX); ++ tcg_out8(s, args[4]); ++ break; ++ case INDEX_op_div_i64: /* Optional (TCG_TARGET_HAS_div_i64). */ ++ case INDEX_op_divu_i64: /* Optional (TCG_TARGET_HAS_div_i64). */ ++ case INDEX_op_rem_i64: /* Optional (TCG_TARGET_HAS_div_i64). */ ++ case INDEX_op_remu_i64: /* Optional (TCG_TARGET_HAS_div_i64). */ ++ TODO(); ++ break; ++ case INDEX_op_div2_i64: /* Optional (TCG_TARGET_HAS_div2_i64). */ ++ case INDEX_op_divu2_i64: /* Optional (TCG_TARGET_HAS_div2_i64). */ ++ TODO(); ++ break; ++ case INDEX_op_brcond_i64: ++ tcg_out_r(s, args[0]); ++ tcg_out_ri64(s, const_args[1], args[1]); ++ tcg_out8(s, args[2]); /* condition */ ++ tci_out_label(s, arg_label(args[3])); ++ break; ++ case INDEX_op_bswap16_i64: /* Optional (TCG_TARGET_HAS_bswap16_i64). */ ++ case INDEX_op_bswap32_i64: /* Optional (TCG_TARGET_HAS_bswap32_i64). */ ++ case INDEX_op_bswap64_i64: /* Optional (TCG_TARGET_HAS_bswap64_i64). */ ++ case INDEX_op_not_i64: /* Optional (TCG_TARGET_HAS_not_i64). */ ++ case INDEX_op_neg_i64: /* Optional (TCG_TARGET_HAS_neg_i64). */ ++ case INDEX_op_ext8s_i64: /* Optional (TCG_TARGET_HAS_ext8s_i64). */ ++ case INDEX_op_ext8u_i64: /* Optional (TCG_TARGET_HAS_ext8u_i64). */ ++ case INDEX_op_ext16s_i64: /* Optional (TCG_TARGET_HAS_ext16s_i64). */ ++ case INDEX_op_ext16u_i64: /* Optional (TCG_TARGET_HAS_ext16u_i64). */ ++ case INDEX_op_ext32s_i64: /* Optional (TCG_TARGET_HAS_ext32s_i64). */ ++ case INDEX_op_ext32u_i64: /* Optional (TCG_TARGET_HAS_ext32u_i64). */ ++ case INDEX_op_ext_i32_i64: ++ case INDEX_op_extu_i32_i64: ++#endif /* TCG_TARGET_REG_BITS == 64 */ ++ case INDEX_op_neg_i32: /* Optional (TCG_TARGET_HAS_neg_i32). */ ++ case INDEX_op_not_i32: /* Optional (TCG_TARGET_HAS_not_i32). */ ++ case INDEX_op_ext8s_i32: /* Optional (TCG_TARGET_HAS_ext8s_i32). */ ++ case INDEX_op_ext16s_i32: /* Optional (TCG_TARGET_HAS_ext16s_i32). */ ++ case INDEX_op_ext8u_i32: /* Optional (TCG_TARGET_HAS_ext8u_i32). */ ++ case INDEX_op_ext16u_i32: /* Optional (TCG_TARGET_HAS_ext16u_i32). */ ++ case INDEX_op_bswap16_i32: /* Optional (TCG_TARGET_HAS_bswap16_i32). */ ++ case INDEX_op_bswap32_i32: /* Optional (TCG_TARGET_HAS_bswap32_i32). */ ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ break; ++ case INDEX_op_div_i32: /* Optional (TCG_TARGET_HAS_div_i32). */ ++ case INDEX_op_divu_i32: /* Optional (TCG_TARGET_HAS_div_i32). */ ++ case INDEX_op_rem_i32: /* Optional (TCG_TARGET_HAS_div_i32). */ ++ case INDEX_op_remu_i32: /* Optional (TCG_TARGET_HAS_div_i32). */ ++ tcg_out_r(s, args[0]); ++ tcg_out_ri32(s, const_args[1], args[1]); ++ tcg_out_ri32(s, const_args[2], args[2]); ++ break; ++ case INDEX_op_div2_i32: /* Optional (TCG_TARGET_HAS_div2_i32). */ ++ case INDEX_op_divu2_i32: /* Optional (TCG_TARGET_HAS_div2_i32). */ ++ TODO(); ++ break; ++#if TCG_TARGET_REG_BITS == 32 ++ case INDEX_op_add2_i32: ++ case INDEX_op_sub2_i32: ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ tcg_out_r(s, args[2]); ++ tcg_out_r(s, args[3]); ++ tcg_out_r(s, args[4]); ++ tcg_out_r(s, args[5]); ++ break; ++ case INDEX_op_brcond2_i32: ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ tcg_out_ri32(s, const_args[2], args[2]); ++ tcg_out_ri32(s, const_args[3], args[3]); ++ tcg_out8(s, args[4]); /* condition */ ++ tci_out_label(s, arg_label(args[5])); ++ break; ++ case INDEX_op_mulu2_i32: ++ tcg_out_r(s, args[0]); ++ tcg_out_r(s, args[1]); ++ tcg_out_r(s, args[2]); ++ tcg_out_r(s, args[3]); ++ break; ++#endif ++ case INDEX_op_brcond_i32: ++ tcg_out_r(s, args[0]); ++ tcg_out_ri32(s, const_args[1], args[1]); ++ tcg_out8(s, args[2]); /* condition */ ++ tci_out_label(s, arg_label(args[3])); ++ break; ++ case INDEX_op_qemu_ld_i32: ++ tcg_out_r(s, *args++); ++ tcg_out_r(s, *args++); ++ if (TARGET_LONG_BITS > TCG_TARGET_REG_BITS) { ++ tcg_out_r(s, *args++); ++ } ++ tcg_out_i(s, *args++); ++ break; ++ case INDEX_op_qemu_ld_i64: ++ tcg_out_r(s, *args++); ++ if (TCG_TARGET_REG_BITS == 32) { ++ tcg_out_r(s, *args++); ++ } ++ tcg_out_r(s, *args++); ++ if (TARGET_LONG_BITS > TCG_TARGET_REG_BITS) { ++ tcg_out_r(s, *args++); ++ } ++ tcg_out_i(s, *args++); ++ break; ++ case INDEX_op_qemu_st_i32: ++ tcg_out_r(s, *args++); ++ tcg_out_r(s, *args++); ++ if (TARGET_LONG_BITS > TCG_TARGET_REG_BITS) { ++ tcg_out_r(s, *args++); ++ } ++ tcg_out_i(s, *args++); ++ break; ++ case INDEX_op_qemu_st_i64: ++ tcg_out_r(s, *args++); ++ if (TCG_TARGET_REG_BITS == 32) { ++ tcg_out_r(s, *args++); ++ } ++ tcg_out_r(s, *args++); ++ if (TARGET_LONG_BITS > TCG_TARGET_REG_BITS) { ++ tcg_out_r(s, *args++); ++ } ++ tcg_out_i(s, *args++); ++ break; ++ case INDEX_op_mb: ++ break; ++ case INDEX_op_mov_i32: /* Always emitted via tcg_out_mov. */ ++ case INDEX_op_mov_i64: ++ case INDEX_op_movi_i32: /* Always emitted via tcg_out_movi. */ ++ case INDEX_op_movi_i64: ++ case INDEX_op_call: /* Always emitted via tcg_out_call. */ ++ default: ++ tcg_abort(); ++ } ++ old_code_ptr[1] = s->code_ptr - old_code_ptr; ++} ++ ++static void tcg_out_st(TCGContext *s, TCGType type, TCGReg arg, TCGReg arg1, ++ intptr_t arg2) ++{ ++ uint8_t *old_code_ptr = s->code_ptr; ++ if (type == TCG_TYPE_I32) { ++ tcg_out_op_t(s, INDEX_op_st_i32); ++ tcg_out_r(s, arg); ++ tcg_out_r(s, arg1); ++ tcg_out32(s, arg2); ++ } else { ++ tcg_debug_assert(type == TCG_TYPE_I64); ++#if TCG_TARGET_REG_BITS == 64 ++ tcg_out_op_t(s, INDEX_op_st_i64); ++ tcg_out_r(s, arg); ++ tcg_out_r(s, arg1); ++ tcg_out32(s, arg2); ++#else ++ TODO(); ++#endif ++ } ++ old_code_ptr[1] = s->code_ptr - old_code_ptr; ++} ++ ++static inline bool tcg_out_sti(TCGContext *s, TCGType type, TCGArg val, ++ TCGReg base, intptr_t ofs) ++{ ++ return false; ++} ++ ++/* Test if a constant matches the constraint. */ ++static int tcg_target_const_match(tcg_target_long val, TCGType type, ++ const TCGArgConstraint *arg_ct) ++{ ++ /* No need to return 0 or 1, 0 or != 0 is good enough. */ ++ return arg_ct->ct & TCG_CT_CONST; ++} ++ ++static void tcg_target_init(TCGContext *s) ++{ ++#if defined(CONFIG_DEBUG_TCG_INTERPRETER) ++ const char *envval = getenv("DEBUG_TCG"); ++ if (envval) { ++ qemu_set_log(strtol(envval, NULL, 0)); ++ } ++#endif ++ ++ /* The current code uses uint8_t for tcg operations. unicorn moved the ++ op-def table into TCGContext, so the historical tcg_op_defs_max bound ++ check no longer applies; NB_OPS stays far below UINT8_MAX. */ ++ ++ /* Registers available for 32 bit operations. */ ++ s->tcg_target_available_regs[TCG_TYPE_I32] = BIT(TCG_TARGET_NB_REGS) - 1; ++ /* Registers available for 64 bit operations. */ ++ s->tcg_target_available_regs[TCG_TYPE_I64] = BIT(TCG_TARGET_NB_REGS) - 1; ++ /* TODO: Which registers should be set here? */ ++ s->tcg_target_call_clobber_regs = BIT(TCG_TARGET_NB_REGS) - 1; ++ ++ s->reserved_regs = 0; ++ tcg_regset_set_reg(s->reserved_regs, TCG_REG_CALL_STACK); ++ ++ /* We use negative offsets from "sp" so that we can distinguish ++ stores that might pretend to be call arguments. */ ++ tcg_set_frame(s, TCG_REG_CALL_STACK, ++ -CPU_TEMP_BUF_NLONGS * sizeof(long), ++ CPU_TEMP_BUF_NLONGS * sizeof(long)); ++} ++ ++/* Generate global QEMU prologue and epilogue code. */ ++static inline void tcg_target_qemu_prologue(TCGContext *s) ++{ ++} +diff --git a/qemu/tcg/tci.c b/qemu/tcg/tci.c +new file mode 100644 +index 0000000..1f330b5 +--- /dev/null ++++ b/qemu/tcg/tci.c +@@ -0,0 +1,1300 @@ ++/* ++ * Tiny Code Interpreter for QEMU ++ * ++ * Copyright (c) 2009, 2011, 2016 Stefan Weil ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++#include "qemu/osdep.h" ++ ++/* Enable TCI assertions only when debugging TCG (and without NDEBUG defined). ++ * Without assertions, the interpreter runs much faster. */ ++#if defined(CONFIG_DEBUG_TCG) ++# define tci_assert(cond) assert(cond) ++#else ++# define tci_assert(cond) ((void)0) ++#endif ++ ++#include "qemu-common.h" ++#include "tcg/tcg.h" /* MAX_OPC_PARAM_IARGS */ ++#include "exec/cpu_ldst.h" ++#include "tcg/tcg-op.h" ++ ++#ifdef __EMSCRIPTEN__ ++#include ++/* Wall-clock deadline (ms since epoch; 0 = unlimited) set by the engine glue ++ before uc_emu_start; the interpreter checks it periodically because wasm ++ cannot host unicorn's timeout thread. */ ++double tci_wasm_deadline_ms; ++#endif ++ ++/* qemu 5.0's TCI never defined this global (the extern lives in ++ include/exec/exec-all.h under CONFIG_TCG_INTERPRETER). */ ++uintptr_t tci_tb_ptr; ++ ++ ++/* Marker for missing code. */ ++#define TODO() \ ++ do { \ ++ fprintf(stderr, "TODO %s:%u: %s()\n", \ ++ __FILE__, __LINE__, __func__); \ ++ tcg_abort(); \ ++ } while (0) ++ ++#if MAX_OPC_PARAM_IARGS != 6 ++# error Fix needed, number of supported input arguments changed! ++#endif ++#if TCG_TARGET_REG_BITS == 32 ++typedef uint64_t (*helper_function)(tcg_target_ulong, tcg_target_ulong, ++ tcg_target_ulong, tcg_target_ulong, ++ tcg_target_ulong, tcg_target_ulong, ++ tcg_target_ulong, tcg_target_ulong, ++ tcg_target_ulong, tcg_target_ulong, ++ tcg_target_ulong, tcg_target_ulong); ++#else ++typedef uint64_t (*helper_function)(tcg_target_ulong, tcg_target_ulong, ++ tcg_target_ulong, tcg_target_ulong, ++ tcg_target_ulong, tcg_target_ulong); ++#endif ++ ++static tcg_target_ulong tci_read_reg(const tcg_target_ulong *regs, TCGReg index) ++{ ++ tci_assert(index < TCG_TARGET_NB_REGS); ++ return regs[index]; ++} ++ ++#if TCG_TARGET_HAS_ext8s_i32 || TCG_TARGET_HAS_ext8s_i64 ++static int8_t tci_read_reg8s(const tcg_target_ulong *regs, TCGReg index) ++{ ++ return (int8_t)tci_read_reg(regs, index); ++} ++#endif ++ ++#if TCG_TARGET_HAS_ext16s_i32 || TCG_TARGET_HAS_ext16s_i64 ++static int16_t tci_read_reg16s(const tcg_target_ulong *regs, TCGReg index) ++{ ++ return (int16_t)tci_read_reg(regs, index); ++} ++#endif ++ ++#if TCG_TARGET_REG_BITS == 64 ++static int32_t tci_read_reg32s(const tcg_target_ulong *regs, TCGReg index) ++{ ++ return (int32_t)tci_read_reg(regs, index); ++} ++#endif ++ ++static uint8_t tci_read_reg8(const tcg_target_ulong *regs, TCGReg index) ++{ ++ return (uint8_t)tci_read_reg(regs, index); ++} ++ ++static uint16_t tci_read_reg16(const tcg_target_ulong *regs, TCGReg index) ++{ ++ return (uint16_t)tci_read_reg(regs, index); ++} ++ ++static uint32_t tci_read_reg32(const tcg_target_ulong *regs, TCGReg index) ++{ ++ return (uint32_t)tci_read_reg(regs, index); ++} ++ ++#if TCG_TARGET_REG_BITS == 64 ++static uint64_t tci_read_reg64(const tcg_target_ulong *regs, TCGReg index) ++{ ++ return tci_read_reg(regs, index); ++} ++#endif ++ ++static void ++tci_write_reg(tcg_target_ulong *regs, TCGReg index, tcg_target_ulong value) ++{ ++ tci_assert(index < TCG_TARGET_NB_REGS); ++ tci_assert(index != TCG_AREG0); ++ tci_assert(index != TCG_REG_CALL_STACK); ++ regs[index] = value; ++} ++ ++#if TCG_TARGET_REG_BITS == 64 ++static void ++tci_write_reg32s(tcg_target_ulong *regs, TCGReg index, int32_t value) ++{ ++ tci_write_reg(regs, index, value); ++} ++#endif ++ ++static void tci_write_reg8(tcg_target_ulong *regs, TCGReg index, uint8_t value) ++{ ++ tci_write_reg(regs, index, value); ++} ++ ++static void ++tci_write_reg16(tcg_target_ulong *regs, TCGReg index, uint16_t value) ++{ ++ tci_write_reg(regs, index, value); ++} ++ ++static void ++tci_write_reg32(tcg_target_ulong *regs, TCGReg index, uint32_t value) ++{ ++ tci_write_reg(regs, index, value); ++} ++ ++#if TCG_TARGET_REG_BITS == 32 ++static void tci_write_reg64(tcg_target_ulong *regs, uint32_t high_index, ++ uint32_t low_index, uint64_t value) ++{ ++ tci_write_reg(regs, low_index, value); ++ tci_write_reg(regs, high_index, value >> 32); ++} ++#elif TCG_TARGET_REG_BITS == 64 ++static void ++tci_write_reg64(tcg_target_ulong *regs, TCGReg index, uint64_t value) ++{ ++ tci_write_reg(regs, index, value); ++} ++#endif ++ ++#if TCG_TARGET_REG_BITS == 32 ++/* Create a 64 bit value from two 32 bit values. */ ++static uint64_t tci_uint64(uint32_t high, uint32_t low) ++{ ++ return ((uint64_t)high << 32) + low; ++} ++#endif ++ ++/* Read constant (native size) from bytecode. */ ++static tcg_target_ulong tci_read_i(uint8_t **tb_ptr) ++{ ++ tcg_target_ulong value = *(tcg_target_ulong *)(*tb_ptr); ++ *tb_ptr += sizeof(value); ++ return value; ++} ++ ++/* Read unsigned constant (32 bit) from bytecode. */ ++static uint32_t tci_read_i32(uint8_t **tb_ptr) ++{ ++ uint32_t value = *(uint32_t *)(*tb_ptr); ++ *tb_ptr += sizeof(value); ++ return value; ++} ++ ++/* Read signed constant (32 bit) from bytecode. */ ++static int32_t tci_read_s32(uint8_t **tb_ptr) ++{ ++ int32_t value = *(int32_t *)(*tb_ptr); ++ *tb_ptr += sizeof(value); ++ return value; ++} ++ ++#if TCG_TARGET_REG_BITS == 64 ++/* Read constant (64 bit) from bytecode. */ ++static uint64_t tci_read_i64(uint8_t **tb_ptr) ++{ ++ uint64_t value = *(uint64_t *)(*tb_ptr); ++ *tb_ptr += sizeof(value); ++ return value; ++} ++#endif ++ ++/* Read indexed register (native size) from bytecode. */ ++static tcg_target_ulong ++tci_read_r(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ tcg_target_ulong value = tci_read_reg(regs, **tb_ptr); ++ *tb_ptr += 1; ++ return value; ++} ++ ++/* Read indexed register (8 bit) from bytecode. */ ++static uint8_t tci_read_r8(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ uint8_t value = tci_read_reg8(regs, **tb_ptr); ++ *tb_ptr += 1; ++ return value; ++} ++ ++#if TCG_TARGET_HAS_ext8s_i32 || TCG_TARGET_HAS_ext8s_i64 ++/* Read indexed register (8 bit signed) from bytecode. */ ++static int8_t tci_read_r8s(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ int8_t value = tci_read_reg8s(regs, **tb_ptr); ++ *tb_ptr += 1; ++ return value; ++} ++#endif ++ ++/* Read indexed register (16 bit) from bytecode. */ ++static uint16_t tci_read_r16(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ uint16_t value = tci_read_reg16(regs, **tb_ptr); ++ *tb_ptr += 1; ++ return value; ++} ++ ++#if TCG_TARGET_HAS_ext16s_i32 || TCG_TARGET_HAS_ext16s_i64 ++/* Read indexed register (16 bit signed) from bytecode. */ ++static int16_t tci_read_r16s(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ int16_t value = tci_read_reg16s(regs, **tb_ptr); ++ *tb_ptr += 1; ++ return value; ++} ++#endif ++ ++/* Read indexed register (32 bit) from bytecode. */ ++static uint32_t tci_read_r32(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ uint32_t value = tci_read_reg32(regs, **tb_ptr); ++ *tb_ptr += 1; ++ return value; ++} ++ ++#if TCG_TARGET_REG_BITS == 32 ++/* Read two indexed registers (2 * 32 bit) from bytecode. */ ++static uint64_t tci_read_r64(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ uint32_t low = tci_read_r32(regs, tb_ptr); ++ return tci_uint64(tci_read_r32(regs, tb_ptr), low); ++} ++#elif TCG_TARGET_REG_BITS == 64 ++/* Read indexed register (32 bit signed) from bytecode. */ ++static int32_t tci_read_r32s(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ int32_t value = tci_read_reg32s(regs, **tb_ptr); ++ *tb_ptr += 1; ++ return value; ++} ++ ++/* Read indexed register (64 bit) from bytecode. */ ++static uint64_t tci_read_r64(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ uint64_t value = tci_read_reg64(regs, **tb_ptr); ++ *tb_ptr += 1; ++ return value; ++} ++#endif ++ ++/* Read indexed register(s) with target address from bytecode. */ ++static target_ulong ++tci_read_ulong(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ target_ulong taddr = tci_read_r(regs, tb_ptr); ++#if TARGET_LONG_BITS > TCG_TARGET_REG_BITS ++ taddr += (uint64_t)tci_read_r(regs, tb_ptr) << 32; ++#endif ++ return taddr; ++} ++ ++/* Read indexed register or constant (native size) from bytecode. */ ++static tcg_target_ulong ++tci_read_ri(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ tcg_target_ulong value; ++ TCGReg r = **tb_ptr; ++ *tb_ptr += 1; ++ if (r == TCG_CONST) { ++ value = tci_read_i(tb_ptr); ++ } else { ++ value = tci_read_reg(regs, r); ++ } ++ return value; ++} ++ ++/* Read indexed register or constant (32 bit) from bytecode. */ ++static uint32_t tci_read_ri32(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ uint32_t value; ++ TCGReg r = **tb_ptr; ++ *tb_ptr += 1; ++ if (r == TCG_CONST) { ++ value = tci_read_i32(tb_ptr); ++ } else { ++ value = tci_read_reg32(regs, r); ++ } ++ return value; ++} ++ ++#if TCG_TARGET_REG_BITS == 32 ++/* Read two indexed registers or constants (2 * 32 bit) from bytecode. */ ++static uint64_t tci_read_ri64(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ uint32_t low = tci_read_ri32(regs, tb_ptr); ++ return tci_uint64(tci_read_ri32(regs, tb_ptr), low); ++} ++#elif TCG_TARGET_REG_BITS == 64 ++/* Read indexed register or constant (64 bit) from bytecode. */ ++static uint64_t tci_read_ri64(const tcg_target_ulong *regs, uint8_t **tb_ptr) ++{ ++ uint64_t value; ++ TCGReg r = **tb_ptr; ++ *tb_ptr += 1; ++ if (r == TCG_CONST) { ++ value = tci_read_i64(tb_ptr); ++ } else { ++ value = tci_read_reg64(regs, r); ++ } ++ return value; ++} ++#endif ++ ++static tcg_target_ulong tci_read_label(uint8_t **tb_ptr) ++{ ++ tcg_target_ulong label = tci_read_i(tb_ptr); ++ tci_assert(label != 0); ++ return label; ++} ++ ++static bool tci_compare32(uint32_t u0, uint32_t u1, TCGCond condition) ++{ ++ bool result = false; ++ int32_t i0 = u0; ++ int32_t i1 = u1; ++ switch (condition) { ++ case TCG_COND_EQ: ++ result = (u0 == u1); ++ break; ++ case TCG_COND_NE: ++ result = (u0 != u1); ++ break; ++ case TCG_COND_LT: ++ result = (i0 < i1); ++ break; ++ case TCG_COND_GE: ++ result = (i0 >= i1); ++ break; ++ case TCG_COND_LE: ++ result = (i0 <= i1); ++ break; ++ case TCG_COND_GT: ++ result = (i0 > i1); ++ break; ++ case TCG_COND_LTU: ++ result = (u0 < u1); ++ break; ++ case TCG_COND_GEU: ++ result = (u0 >= u1); ++ break; ++ case TCG_COND_LEU: ++ result = (u0 <= u1); ++ break; ++ case TCG_COND_GTU: ++ result = (u0 > u1); ++ break; ++ default: ++ TODO(); ++ } ++ return result; ++} ++ ++static bool tci_compare64(uint64_t u0, uint64_t u1, TCGCond condition) ++{ ++ bool result = false; ++ int64_t i0 = u0; ++ int64_t i1 = u1; ++ switch (condition) { ++ case TCG_COND_EQ: ++ result = (u0 == u1); ++ break; ++ case TCG_COND_NE: ++ result = (u0 != u1); ++ break; ++ case TCG_COND_LT: ++ result = (i0 < i1); ++ break; ++ case TCG_COND_GE: ++ result = (i0 >= i1); ++ break; ++ case TCG_COND_LE: ++ result = (i0 <= i1); ++ break; ++ case TCG_COND_GT: ++ result = (i0 > i1); ++ break; ++ case TCG_COND_LTU: ++ result = (u0 < u1); ++ break; ++ case TCG_COND_GEU: ++ result = (u0 >= u1); ++ break; ++ case TCG_COND_LEU: ++ result = (u0 <= u1); ++ break; ++ case TCG_COND_GTU: ++ result = (u0 > u1); ++ break; ++ default: ++ TODO(); ++ } ++ return result; ++} ++ ++#ifdef CONFIG_SOFTMMU ++# define qemu_ld_ub \ ++ helper_ret_ldub_mmu(env, taddr, oi, (uintptr_t)tb_ptr) ++# define qemu_ld_leuw \ ++ helper_le_lduw_mmu(env, taddr, oi, (uintptr_t)tb_ptr) ++# define qemu_ld_leul \ ++ helper_le_ldul_mmu(env, taddr, oi, (uintptr_t)tb_ptr) ++# define qemu_ld_leq \ ++ helper_le_ldq_mmu(env, taddr, oi, (uintptr_t)tb_ptr) ++# define qemu_ld_beuw \ ++ helper_be_lduw_mmu(env, taddr, oi, (uintptr_t)tb_ptr) ++# define qemu_ld_beul \ ++ helper_be_ldul_mmu(env, taddr, oi, (uintptr_t)tb_ptr) ++# define qemu_ld_beq \ ++ helper_be_ldq_mmu(env, taddr, oi, (uintptr_t)tb_ptr) ++# define qemu_st_b(X) \ ++ helper_ret_stb_mmu(env, taddr, X, oi, (uintptr_t)tb_ptr) ++# define qemu_st_lew(X) \ ++ helper_le_stw_mmu(env, taddr, X, oi, (uintptr_t)tb_ptr) ++# define qemu_st_lel(X) \ ++ helper_le_stl_mmu(env, taddr, X, oi, (uintptr_t)tb_ptr) ++# define qemu_st_leq(X) \ ++ helper_le_stq_mmu(env, taddr, X, oi, (uintptr_t)tb_ptr) ++# define qemu_st_bew(X) \ ++ helper_be_stw_mmu(env, taddr, X, oi, (uintptr_t)tb_ptr) ++# define qemu_st_bel(X) \ ++ helper_be_stl_mmu(env, taddr, X, oi, (uintptr_t)tb_ptr) ++# define qemu_st_beq(X) \ ++ helper_be_stq_mmu(env, taddr, X, oi, (uintptr_t)tb_ptr) ++#else ++# define qemu_ld_ub ldub_p(g2h(taddr)) ++# define qemu_ld_leuw lduw_le_p(g2h(taddr)) ++# define qemu_ld_leul (uint32_t)ldl_le_p(g2h(taddr)) ++# define qemu_ld_leq ldq_le_p(g2h(taddr)) ++# define qemu_ld_beuw lduw_be_p(g2h(taddr)) ++# define qemu_ld_beul (uint32_t)ldl_be_p(g2h(taddr)) ++# define qemu_ld_beq ldq_be_p(g2h(taddr)) ++# define qemu_st_b(X) stb_p(g2h(taddr), X) ++# define qemu_st_lew(X) stw_le_p(g2h(taddr), X) ++# define qemu_st_lel(X) stl_le_p(g2h(taddr), X) ++# define qemu_st_leq(X) stq_le_p(g2h(taddr), X) ++# define qemu_st_bew(X) stw_be_p(g2h(taddr), X) ++# define qemu_st_bel(X) stl_be_p(g2h(taddr), X) ++# define qemu_st_beq(X) stq_be_p(g2h(taddr), X) ++#endif ++ ++/* Interpret pseudo code in tb. */ ++uintptr_t tcg_qemu_tb_exec(CPUArchState *env, uint8_t *tb_ptr) ++{ ++ tcg_target_ulong regs[TCG_TARGET_NB_REGS]; ++ long tcg_temps[CPU_TEMP_BUF_NLONGS]; ++ uintptr_t sp_value = (uintptr_t)(tcg_temps + CPU_TEMP_BUF_NLONGS); ++ uintptr_t ret = 0; ++ ++ regs[TCG_AREG0] = (tcg_target_ulong)env; ++ regs[TCG_REG_CALL_STACK] = sp_value; ++ tci_assert(tb_ptr); ++ ++#ifdef __EMSCRIPTEN__ ++ unsigned tci_deadline_slice = 0; ++#endif ++ for (;;) { ++ TCGOpcode opc = tb_ptr[0]; ++#ifdef __EMSCRIPTEN__ ++ /* Periodic wall-clock deadline enforcement (no timeout thread). */ ++ if (tci_wasm_deadline_ms != 0 && (++tci_deadline_slice & 0xffff) == 0) { ++ if (emscripten_get_now() > tci_wasm_deadline_ms) { ++ uc_emu_stop(env_cpu(env)->uc); ++ return 0; ++ } ++ } ++#endif ++#if defined(CONFIG_DEBUG_TCG) && !defined(NDEBUG) ++ uint8_t op_size = tb_ptr[1]; ++ uint8_t *old_code_ptr = tb_ptr; ++#endif ++ tcg_target_ulong t0; ++ tcg_target_ulong t1; ++ tcg_target_ulong t2; ++ tcg_target_ulong label; ++ TCGCond condition; ++ target_ulong taddr; ++ uint8_t tmp8; ++ uint16_t tmp16; ++ uint32_t tmp32; ++ uint64_t tmp64; ++#if TCG_TARGET_REG_BITS == 32 ++ uint64_t v64; ++#endif ++ TCGMemOpIdx oi; ++ ++#if defined(GETPC) ++ tci_tb_ptr = (uintptr_t)tb_ptr; ++#endif ++ ++ /* Skip opcode and size entry. */ ++ tb_ptr += 2; ++ ++ switch (opc) { ++ case INDEX_op_call: ++ t0 = tci_read_ri(regs, &tb_ptr); ++#if TCG_TARGET_REG_BITS == 32 ++ tmp64 = ((helper_function)t0)(tci_read_reg(regs, TCG_REG_R0), ++ tci_read_reg(regs, TCG_REG_R1), ++ tci_read_reg(regs, TCG_REG_R2), ++ tci_read_reg(regs, TCG_REG_R3), ++ tci_read_reg(regs, TCG_REG_R5), ++ tci_read_reg(regs, TCG_REG_R6), ++ tci_read_reg(regs, TCG_REG_R7), ++ tci_read_reg(regs, TCG_REG_R8), ++ tci_read_reg(regs, TCG_REG_R9), ++ tci_read_reg(regs, TCG_REG_R10), ++ tci_read_reg(regs, TCG_REG_R11), ++ tci_read_reg(regs, TCG_REG_R12)); ++ tci_write_reg(regs, TCG_REG_R0, tmp64); ++ tci_write_reg(regs, TCG_REG_R1, tmp64 >> 32); ++#else ++ tmp64 = ((helper_function)t0)(tci_read_reg(regs, TCG_REG_R0), ++ tci_read_reg(regs, TCG_REG_R1), ++ tci_read_reg(regs, TCG_REG_R2), ++ tci_read_reg(regs, TCG_REG_R3), ++ tci_read_reg(regs, TCG_REG_R5), ++ tci_read_reg(regs, TCG_REG_R6)); ++ tci_write_reg(regs, TCG_REG_R0, tmp64); ++#endif ++ break; ++ case INDEX_op_br: ++ label = tci_read_label(&tb_ptr); ++ tci_assert(tb_ptr == old_code_ptr + op_size); ++ tb_ptr = (uint8_t *)label; ++ continue; ++ case INDEX_op_setcond_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ condition = *tb_ptr++; ++ tci_write_reg32(regs, t0, tci_compare32(t1, t2, condition)); ++ break; ++#if TCG_TARGET_REG_BITS == 32 ++ case INDEX_op_setcond2_i32: ++ t0 = *tb_ptr++; ++ tmp64 = tci_read_r64(regs, &tb_ptr); ++ v64 = tci_read_ri64(regs, &tb_ptr); ++ condition = *tb_ptr++; ++ tci_write_reg32(regs, t0, tci_compare64(tmp64, v64, condition)); ++ break; ++#elif TCG_TARGET_REG_BITS == 64 ++ case INDEX_op_setcond_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ condition = *tb_ptr++; ++ tci_write_reg64(regs, t0, tci_compare64(t1, t2, condition)); ++ break; ++#endif ++ case INDEX_op_mov_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1); ++ break; ++ case INDEX_op_movi_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_i32(&tb_ptr); ++ tci_write_reg32(regs, t0, t1); ++ break; ++ ++ /* Load/store operations (32 bit). */ ++ ++ case INDEX_op_ld8u_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ tci_write_reg8(regs, t0, *(uint8_t *)(t1 + t2)); ++ break; ++ case INDEX_op_ld8s_i32: ++ TODO(); ++ break; ++ case INDEX_op_ld16u_i32: ++ TODO(); ++ break; ++ case INDEX_op_ld16s_i32: ++ TODO(); ++ break; ++ case INDEX_op_ld_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ tci_write_reg32(regs, t0, *(uint32_t *)(t1 + t2)); ++ break; ++ case INDEX_op_st8_i32: ++ t0 = tci_read_r8(regs, &tb_ptr); ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ *(uint8_t *)(t1 + t2) = t0; ++ break; ++ case INDEX_op_st16_i32: ++ t0 = tci_read_r16(regs, &tb_ptr); ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ *(uint16_t *)(t1 + t2) = t0; ++ break; ++ case INDEX_op_st_i32: ++ t0 = tci_read_r32(regs, &tb_ptr); ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ tci_assert(t1 != sp_value || (int32_t)t2 < 0); ++ *(uint32_t *)(t1 + t2) = t0; ++ break; ++ ++ /* Arithmetic operations (32 bit). */ ++ ++ case INDEX_op_add_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 + t2); ++ break; ++ case INDEX_op_sub_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 - t2); ++ break; ++ case INDEX_op_mul_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 * t2); ++ break; ++#if TCG_TARGET_HAS_div_i32 ++ case INDEX_op_div_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, (int32_t)t1 / (int32_t)t2); ++ break; ++ case INDEX_op_divu_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 / t2); ++ break; ++ case INDEX_op_rem_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, (int32_t)t1 % (int32_t)t2); ++ break; ++ case INDEX_op_remu_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 % t2); ++ break; ++#elif TCG_TARGET_HAS_div2_i32 ++ case INDEX_op_div2_i32: ++ case INDEX_op_divu2_i32: ++ TODO(); ++ break; ++#endif ++ case INDEX_op_and_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 & t2); ++ break; ++ case INDEX_op_or_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 | t2); ++ break; ++ case INDEX_op_xor_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 ^ t2); ++ break; ++ ++ /* Shift/rotate operations (32 bit). */ ++ ++ case INDEX_op_shl_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 << (t2 & 31)); ++ break; ++ case INDEX_op_shr_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1 >> (t2 & 31)); ++ break; ++ case INDEX_op_sar_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, ((int32_t)t1 >> (t2 & 31))); ++ break; ++#if TCG_TARGET_HAS_rot_i32 ++ case INDEX_op_rotl_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, rol32(t1, t2 & 31)); ++ break; ++ case INDEX_op_rotr_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ t2 = tci_read_ri32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, ror32(t1, t2 & 31)); ++ break; ++#endif ++#if TCG_TARGET_HAS_deposit_i32 ++ case INDEX_op_deposit_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r32(regs, &tb_ptr); ++ t2 = tci_read_r32(regs, &tb_ptr); ++ tmp16 = *tb_ptr++; ++ tmp8 = *tb_ptr++; ++ tmp32 = (((1 << tmp8) - 1) << tmp16); ++ tci_write_reg32(regs, t0, (t1 & ~tmp32) | ((t2 << tmp16) & tmp32)); ++ break; ++#endif ++ case INDEX_op_brcond_i32: ++ t0 = tci_read_r32(regs, &tb_ptr); ++ t1 = tci_read_ri32(regs, &tb_ptr); ++ condition = *tb_ptr++; ++ label = tci_read_label(&tb_ptr); ++ if (tci_compare32(t0, t1, condition)) { ++ tci_assert(tb_ptr == old_code_ptr + op_size); ++ tb_ptr = (uint8_t *)label; ++ continue; ++ } ++ break; ++#if TCG_TARGET_REG_BITS == 32 ++ case INDEX_op_add2_i32: ++ t0 = *tb_ptr++; ++ t1 = *tb_ptr++; ++ tmp64 = tci_read_r64(regs, &tb_ptr); ++ tmp64 += tci_read_r64(regs, &tb_ptr); ++ tci_write_reg64(regs, t1, t0, tmp64); ++ break; ++ case INDEX_op_sub2_i32: ++ t0 = *tb_ptr++; ++ t1 = *tb_ptr++; ++ tmp64 = tci_read_r64(regs, &tb_ptr); ++ tmp64 -= tci_read_r64(regs, &tb_ptr); ++ tci_write_reg64(regs, t1, t0, tmp64); ++ break; ++ case INDEX_op_brcond2_i32: ++ tmp64 = tci_read_r64(regs, &tb_ptr); ++ v64 = tci_read_ri64(regs, &tb_ptr); ++ condition = *tb_ptr++; ++ label = tci_read_label(&tb_ptr); ++ if (tci_compare64(tmp64, v64, condition)) { ++ tci_assert(tb_ptr == old_code_ptr + op_size); ++ tb_ptr = (uint8_t *)label; ++ continue; ++ } ++ break; ++ case INDEX_op_mulu2_i32: ++ t0 = *tb_ptr++; ++ t1 = *tb_ptr++; ++ t2 = tci_read_r32(regs, &tb_ptr); ++ tmp64 = tci_read_r32(regs, &tb_ptr); ++ tci_write_reg64(regs, t1, t0, t2 * tmp64); ++ break; ++#endif /* TCG_TARGET_REG_BITS == 32 */ ++#if TCG_TARGET_HAS_ext8s_i32 ++ case INDEX_op_ext8s_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r8s(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_ext16s_i32 ++ case INDEX_op_ext16s_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r16s(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_ext8u_i32 ++ case INDEX_op_ext8u_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r8(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_ext16u_i32 ++ case INDEX_op_ext16u_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r16(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_bswap16_i32 ++ case INDEX_op_bswap16_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r16(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, bswap16(t1)); ++ break; ++#endif ++#if TCG_TARGET_HAS_bswap32_i32 ++ case INDEX_op_bswap32_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, bswap32(t1)); ++ break; ++#endif ++#if TCG_TARGET_HAS_not_i32 ++ case INDEX_op_not_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, ~t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_neg_i32 ++ case INDEX_op_neg_i32: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r32(regs, &tb_ptr); ++ tci_write_reg32(regs, t0, -t1); ++ break; ++#endif ++#if TCG_TARGET_REG_BITS == 64 ++ case INDEX_op_mov_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1); ++ break; ++ case INDEX_op_movi_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_i64(&tb_ptr); ++ tci_write_reg64(regs, t0, t1); ++ break; ++ ++ /* Load/store operations (64 bit). */ ++ ++ case INDEX_op_ld8u_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ tci_write_reg8(regs, t0, *(uint8_t *)(t1 + t2)); ++ break; ++ case INDEX_op_ld8s_i64: ++ TODO(); ++ break; ++ case INDEX_op_ld16u_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ tci_write_reg16(regs, t0, *(uint16_t *)(t1 + t2)); ++ break; ++ case INDEX_op_ld16s_i64: ++ TODO(); ++ break; ++ case INDEX_op_ld32u_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ tci_write_reg32(regs, t0, *(uint32_t *)(t1 + t2)); ++ break; ++ case INDEX_op_ld32s_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ tci_write_reg32s(regs, t0, *(int32_t *)(t1 + t2)); ++ break; ++ case INDEX_op_ld_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ tci_write_reg64(regs, t0, *(uint64_t *)(t1 + t2)); ++ break; ++ case INDEX_op_st8_i64: ++ t0 = tci_read_r8(regs, &tb_ptr); ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ *(uint8_t *)(t1 + t2) = t0; ++ break; ++ case INDEX_op_st16_i64: ++ t0 = tci_read_r16(regs, &tb_ptr); ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ *(uint16_t *)(t1 + t2) = t0; ++ break; ++ case INDEX_op_st32_i64: ++ t0 = tci_read_r32(regs, &tb_ptr); ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ *(uint32_t *)(t1 + t2) = t0; ++ break; ++ case INDEX_op_st_i64: ++ t0 = tci_read_r64(regs, &tb_ptr); ++ t1 = tci_read_r(regs, &tb_ptr); ++ t2 = tci_read_s32(&tb_ptr); ++ tci_assert(t1 != sp_value || (int32_t)t2 < 0); ++ *(uint64_t *)(t1 + t2) = t0; ++ break; ++ ++ /* Arithmetic operations (64 bit). */ ++ ++ case INDEX_op_add_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1 + t2); ++ break; ++ case INDEX_op_sub_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1 - t2); ++ break; ++ case INDEX_op_mul_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1 * t2); ++ break; ++#if TCG_TARGET_HAS_div_i64 ++ case INDEX_op_div_i64: ++ case INDEX_op_divu_i64: ++ case INDEX_op_rem_i64: ++ case INDEX_op_remu_i64: ++ TODO(); ++ break; ++#elif TCG_TARGET_HAS_div2_i64 ++ case INDEX_op_div2_i64: ++ case INDEX_op_divu2_i64: ++ TODO(); ++ break; ++#endif ++ case INDEX_op_and_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1 & t2); ++ break; ++ case INDEX_op_or_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1 | t2); ++ break; ++ case INDEX_op_xor_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1 ^ t2); ++ break; ++ ++ /* Shift/rotate operations (64 bit). */ ++ ++ case INDEX_op_shl_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1 << (t2 & 63)); ++ break; ++ case INDEX_op_shr_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1 >> (t2 & 63)); ++ break; ++ case INDEX_op_sar_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, ((int64_t)t1 >> (t2 & 63))); ++ break; ++#if TCG_TARGET_HAS_rot_i64 ++ case INDEX_op_rotl_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, rol64(t1, t2 & 63)); ++ break; ++ case INDEX_op_rotr_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ t2 = tci_read_ri64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, ror64(t1, t2 & 63)); ++ break; ++#endif ++#if TCG_TARGET_HAS_deposit_i64 ++ case INDEX_op_deposit_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r64(regs, &tb_ptr); ++ t2 = tci_read_r64(regs, &tb_ptr); ++ tmp16 = *tb_ptr++; ++ tmp8 = *tb_ptr++; ++ tmp64 = (((1ULL << tmp8) - 1) << tmp16); ++ tci_write_reg64(regs, t0, (t1 & ~tmp64) | ((t2 << tmp16) & tmp64)); ++ break; ++#endif ++ case INDEX_op_brcond_i64: ++ t0 = tci_read_r64(regs, &tb_ptr); ++ t1 = tci_read_ri64(regs, &tb_ptr); ++ condition = *tb_ptr++; ++ label = tci_read_label(&tb_ptr); ++ if (tci_compare64(t0, t1, condition)) { ++ tci_assert(tb_ptr == old_code_ptr + op_size); ++ tb_ptr = (uint8_t *)label; ++ continue; ++ } ++ break; ++#if TCG_TARGET_HAS_ext8u_i64 ++ case INDEX_op_ext8u_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r8(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_ext8s_i64 ++ case INDEX_op_ext8s_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r8s(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_ext16s_i64 ++ case INDEX_op_ext16s_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r16s(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_ext16u_i64 ++ case INDEX_op_ext16u_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r16(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_ext32s_i64 ++ case INDEX_op_ext32s_i64: ++#endif ++ case INDEX_op_ext_i32_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r32s(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1); ++ break; ++#if TCG_TARGET_HAS_ext32u_i64 ++ case INDEX_op_ext32u_i64: ++#endif ++ case INDEX_op_extu_i32_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r32(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, t1); ++ break; ++#if TCG_TARGET_HAS_bswap16_i64 ++ case INDEX_op_bswap16_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r16(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, bswap16(t1)); ++ break; ++#endif ++#if TCG_TARGET_HAS_bswap32_i64 ++ case INDEX_op_bswap32_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r32(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, bswap32(t1)); ++ break; ++#endif ++#if TCG_TARGET_HAS_bswap64_i64 ++ case INDEX_op_bswap64_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, bswap64(t1)); ++ break; ++#endif ++#if TCG_TARGET_HAS_not_i64 ++ case INDEX_op_not_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, ~t1); ++ break; ++#endif ++#if TCG_TARGET_HAS_neg_i64 ++ case INDEX_op_neg_i64: ++ t0 = *tb_ptr++; ++ t1 = tci_read_r64(regs, &tb_ptr); ++ tci_write_reg64(regs, t0, -t1); ++ break; ++#endif ++#endif /* TCG_TARGET_REG_BITS == 64 */ ++ ++ /* QEMU specific operations. */ ++ ++ case INDEX_op_exit_tb: ++ ret = *(uint64_t *)tb_ptr; ++ goto exit; ++ break; ++ case INDEX_op_goto_tb: ++ /* Jump address is aligned */ ++ tb_ptr = QEMU_ALIGN_PTR_UP(tb_ptr, 4); ++ t0 = atomic_read((int32_t *)tb_ptr); ++ tb_ptr += sizeof(int32_t); ++ tci_assert(tb_ptr == old_code_ptr + op_size); ++ tb_ptr += (int32_t)t0; ++ continue; ++ case INDEX_op_qemu_ld_i32: ++ t0 = *tb_ptr++; ++ taddr = tci_read_ulong(regs, &tb_ptr); ++ oi = tci_read_i(&tb_ptr); ++ switch (get_memop(oi) & (MO_BSWAP | MO_SSIZE)) { ++ case MO_UB: ++ tmp32 = qemu_ld_ub; ++ break; ++ case MO_SB: ++ tmp32 = (int8_t)qemu_ld_ub; ++ break; ++ case MO_LEUW: ++ tmp32 = qemu_ld_leuw; ++ break; ++ case MO_LESW: ++ tmp32 = (int16_t)qemu_ld_leuw; ++ break; ++ case MO_LEUL: ++ tmp32 = qemu_ld_leul; ++ break; ++ case MO_BEUW: ++ tmp32 = qemu_ld_beuw; ++ break; ++ case MO_BESW: ++ tmp32 = (int16_t)qemu_ld_beuw; ++ break; ++ case MO_BEUL: ++ tmp32 = qemu_ld_beul; ++ break; ++ default: ++ tcg_abort(); ++ } ++ tci_write_reg(regs, t0, tmp32); ++ break; ++ case INDEX_op_qemu_ld_i64: ++ t0 = *tb_ptr++; ++ if (TCG_TARGET_REG_BITS == 32) { ++ t1 = *tb_ptr++; ++ } ++ taddr = tci_read_ulong(regs, &tb_ptr); ++ oi = tci_read_i(&tb_ptr); ++ switch (get_memop(oi) & (MO_BSWAP | MO_SSIZE)) { ++ case MO_UB: ++ tmp64 = qemu_ld_ub; ++ break; ++ case MO_SB: ++ tmp64 = (int8_t)qemu_ld_ub; ++ break; ++ case MO_LEUW: ++ tmp64 = qemu_ld_leuw; ++ break; ++ case MO_LESW: ++ tmp64 = (int16_t)qemu_ld_leuw; ++ break; ++ case MO_LEUL: ++ tmp64 = qemu_ld_leul; ++ break; ++ case MO_LESL: ++ tmp64 = (int32_t)qemu_ld_leul; ++ break; ++ case MO_LEQ: ++ tmp64 = qemu_ld_leq; ++ break; ++ case MO_BEUW: ++ tmp64 = qemu_ld_beuw; ++ break; ++ case MO_BESW: ++ tmp64 = (int16_t)qemu_ld_beuw; ++ break; ++ case MO_BEUL: ++ tmp64 = qemu_ld_beul; ++ break; ++ case MO_BESL: ++ tmp64 = (int32_t)qemu_ld_beul; ++ break; ++ case MO_BEQ: ++ tmp64 = qemu_ld_beq; ++ break; ++ default: ++ tcg_abort(); ++ } ++ tci_write_reg(regs, t0, tmp64); ++ if (TCG_TARGET_REG_BITS == 32) { ++ tci_write_reg(regs, t1, tmp64 >> 32); ++ } ++ break; ++ case INDEX_op_qemu_st_i32: ++ t0 = tci_read_r(regs, &tb_ptr); ++ taddr = tci_read_ulong(regs, &tb_ptr); ++ oi = tci_read_i(&tb_ptr); ++ switch (get_memop(oi) & (MO_BSWAP | MO_SIZE)) { ++ case MO_UB: ++ qemu_st_b(t0); ++ break; ++ case MO_LEUW: ++ qemu_st_lew(t0); ++ break; ++ case MO_LEUL: ++ qemu_st_lel(t0); ++ break; ++ case MO_BEUW: ++ qemu_st_bew(t0); ++ break; ++ case MO_BEUL: ++ qemu_st_bel(t0); ++ break; ++ default: ++ tcg_abort(); ++ } ++ break; ++ case INDEX_op_qemu_st_i64: ++ tmp64 = tci_read_r64(regs, &tb_ptr); ++ taddr = tci_read_ulong(regs, &tb_ptr); ++ oi = tci_read_i(&tb_ptr); ++ switch (get_memop(oi) & (MO_BSWAP | MO_SIZE)) { ++ case MO_UB: ++ qemu_st_b(tmp64); ++ break; ++ case MO_LEUW: ++ qemu_st_lew(tmp64); ++ break; ++ case MO_LEUL: ++ qemu_st_lel(tmp64); ++ break; ++ case MO_LEQ: ++ qemu_st_leq(tmp64); ++ break; ++ case MO_BEUW: ++ qemu_st_bew(tmp64); ++ break; ++ case MO_BEUL: ++ qemu_st_bel(tmp64); ++ break; ++ case MO_BEQ: ++ qemu_st_beq(tmp64); ++ break; ++ default: ++ tcg_abort(); ++ } ++ break; ++ case INDEX_op_mb: ++ /* Ensure ordering for all kinds */ ++ smp_mb(); ++ break; ++ default: ++ TODO(); ++ break; ++ } ++ tci_assert(tb_ptr == old_code_ptr + op_size); ++ } ++exit: ++ return ret; ++} +diff --git a/qemu/util/osdep.c b/qemu/util/osdep.c +index 148e37a..a9e5226 100644 +--- a/qemu/util/osdep.c ++++ b/qemu/util/osdep.c +@@ -62,6 +62,11 @@ static int qemu_mprotect__osdep(void *addr, size_t size, int prot) + return -1; + } + return 0; ++#elif defined(__EMSCRIPTEN__) ++ /* WebAssembly has no page protection; the TCI code buffer is plain ++ data (interpreted bytecode), so protection requests are no-ops. */ ++ (void)addr; (void)size; (void)prot; ++ return 0; + #else + if (mprotect(addr, size, prot)) { + // error_report("%s: mprotect failed: %s", __func__, strerror(errno)); +@@ -71,6 +76,13 @@ static int qemu_mprotect__osdep(void *addr, size_t size, int prot) + #endif + } + ++#ifdef __EMSCRIPTEN__ ++#define PROT_NONE 0 ++#define PROT_READ 1 ++#define PROT_WRITE 2 ++#define PROT_EXEC 4 ++#endif ++ + int qemu_mprotect_rwx(void *addr, size_t size) + { + #ifdef _WIN32 +diff --git a/qemu/util/oslib-posix.c b/qemu/util/oslib-posix.c +index 615e477..f470861 100644 +--- a/qemu/util/oslib-posix.c ++++ b/qemu/util/oslib-posix.c +@@ -29,7 +29,11 @@ + #include + #include "qemu/osdep.h" + +-#ifdef CONFIG_LINUX ++#if defined(__EMSCRIPTEN__) ++#include ++#define MAP_SYNC 0x0 ++#define MAP_SHARED_VALIDATE 0x0 ++#elif defined(CONFIG_LINUX) + #include + #else /* !CONFIG_LINUX */ + #define MAP_SYNC 0x0 +@@ -188,6 +192,17 @@ static void *qemu_ram_mmap(struct uc_struct *uc, + void *guardptr; + void *ptr; + ++#ifdef __EMSCRIPTEN__ ++ /* wasm32 has no real mmap (MAP_FIXED cannot be honored), so serve guest ++ RAM from an over-allocated malloc block aligned up in wasm heap. */ ++ { ++ void *raw = malloc(size + align); ++ if (raw == NULL) { ++ return MAP_FAILED; ++ } ++ return (void *)QEMU_ALIGN_UP((uintptr_t)raw, align); ++ } ++#else + /* + * Note: this always allocates at least one extra page of virtual address + * space, even if size is already aligned. +@@ -265,6 +280,7 @@ static void *qemu_ram_mmap(struct uc_struct *uc, + } + + return ptr; ++#endif /* __EMSCRIPTEN__ */ + } + + static void qemu_ram_munmap(struct uc_struct *uc, void *ptr, size_t size) +diff --git a/uc.c b/uc.c +index 75b89a0..2054584 100644 +--- a/uc.c ++++ b/uc.c +@@ -1036,8 +1036,14 @@ static void *_timeout_fn(void *arg) + static void enable_emu_timer(uc_engine *uc, uint64_t timeout) + { + uc->timeout = timeout; ++#ifdef __EMSCRIPTEN__ ++ /* wasm has no threads; the TCI interpreter enforces the wall-clock ++ deadline itself (see tcg/tci.c). */ ++ (void)uc; ++#else + qemu_thread_create(uc, &uc->timer, "timeout", _timeout_fn, uc, + QEMU_THREAD_JOINABLE); ++#endif + } + + static void hook_count_cb(struct uc_struct *uc, uint64_t address, uint32_t size, +@@ -1243,7 +1249,9 @@ uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, + + if (timeout) { + // wait for the timer to finish ++ #ifndef __EMSCRIPTEN__ + qemu_thread_join(&uc->timer); ++#endif + } + + // We may be in a nested uc_emu_start and thus clear invalid_error diff --git a/frontend/src/apple/authenticate.ts b/frontend/src/apple/authenticate.ts index 61550f05..e7c77d7b 100644 --- a/frontend/src/apple/authenticate.ts +++ b/frontend/src/apple/authenticate.ts @@ -3,6 +3,9 @@ import { appleRequest } from "./request"; import { buildPlist, parsePlist } from "./plist"; import { extractAndMergeCookies } from "./cookies"; import { fetchBag, defaultAuthURL } from "./bag"; +import { createSapSigner } from "./sap/client"; +import { loadSapAssets } from "./sap/assets"; +import type { SapSigner } from "./sap/signer"; import i18n from "../i18n"; export class AuthenticationError extends Error { @@ -37,6 +40,20 @@ export async function authenticate( requestHost = authEndpoint.hostname; requestPath = `${authEndpoint.pathname}${authEndpoint.search}`; + // When the bag advertises the SAP signing protocol, every request to the + // auth endpoint must carry X-Apple-ActionSignature over its body bytes. + // The signer sees only the hardware ID and public Apple assets — never the + // password — because signing happens here in the browser. + let sapSigner: SapSigner | null = null; + if (bag.sapEndpoints) { + const assets = await loadSapAssets(); + sapSigner = await createSapSigner({ + ...bag.sapEndpoints, + hardwareID: new TextEncoder().encode(deviceId), + assets, + }); + } + let currentAttempt = 0; let redirectAttempt = 0; @@ -59,6 +76,14 @@ export async function authenticate( "Content-Type": "application/x-apple-plist", }; + if (sapSigner) { + // The signature must cover the exact bytes on the wire; libcurl sends + // the body string as UTF-8, so sign its encoded form. + headers["X-Apple-ActionSignature"] = await sapSigner.sign( + new TextEncoder().encode(plistBody), + ); + } + const response = await appleRequest({ method: "POST", host: requestHost, @@ -149,12 +174,18 @@ export async function authenticate( pod, }; + await sapSigner?.close().catch(() => undefined); + sapSigner = null; return account; } catch (e) { - if (e instanceof AuthenticationError) throw e; + if (e instanceof AuthenticationError) { + await sapSigner?.close().catch(() => undefined); + throw e; + } lastError = e instanceof Error ? e : new Error(String(e)); } } + await sapSigner?.close().catch(() => undefined); throw lastError ?? new Error(i18n.t("errors.auth.unknownReason")); } diff --git a/frontend/src/apple/bag.ts b/frontend/src/apple/bag.ts index ca4f3bc9..0aaf8969 100644 --- a/frontend/src/apple/bag.ts +++ b/frontend/src/apple/bag.ts @@ -1,8 +1,11 @@ import { authHeaders } from "../api/client"; import { parsePlist } from "./plist"; +import type { SapEndpoints } from "./sap/types"; export interface BagOutput { authURL: string; + /** Present when the bag advertises the SAP signing protocol. */ + sapEndpoints?: SapEndpoints; } export const defaultAuthURL = @@ -57,14 +60,29 @@ export async function fetchBag(deviceId: string): Promise { (dict.authenticateAccount as string | undefined) ?? (urlBag?.authenticateAccount as string | undefined); + const bagValue = (key: string): string | undefined => + (dict[key] as string | undefined) ?? + (urlBag?.[key] as string | undefined); + + const setupURL = bagValue("sign-sap-setup"); + const certificateURL = bagValue("sign-sap-setup-cert"); + const versionText = bagValue("sign-sap-version"); + let sapEndpoints: SapEndpoints | undefined; + if (setupURL && certificateURL && versionText) { + const version = Number.parseInt(versionText, 10); + if (Number.isFinite(version)) { + sapEndpoints = { setupURL, certificateURL, version }; + } + } + if (!authURL) { console.warn( "[Bag] authenticateAccount URL not found in bag, using default auth endpoint", ); - return { authURL: defaultAuthURL }; + return { authURL: defaultAuthURL, sapEndpoints }; } - return { authURL: normalizeAuthURL(authURL) }; + return { authURL: normalizeAuthURL(authURL), sapEndpoints }; } catch (error) { console.warn( `[Bag] Failed to fetch/parse bag, using default auth endpoint: ${ diff --git a/frontend/src/apple/sap/assets.ts b/frontend/src/apple/sap/assets.ts new file mode 100644 index 00000000..1a131073 --- /dev/null +++ b/frontend/src/apple/sap/assets.ts @@ -0,0 +1,161 @@ +// SAP asset delivery: the four Apple binaries (CoreFP, CoreFP.icxs, +// CommerceKit, CommerceCore) are extracted once by the backend from a public +// Apple software update package and served from /api/sap-assets. The browser +// caches them in the Cache API keyed by their pinned SHA-256 digests. + +import { authHeaders } from "../../api/client"; +import type { SapAssetBundle } from "./types"; + +export interface SapAssetSpec { + name: string; + /** Size/digest of the distributed (backend-stripped) x86_64 file. */ + size: number; + sha256: string; +} + +/** + * Digests of the distributed assets. The backend strips fat binaries to their + * x86_64 slice (the emulated guest architecture) after verifying Apple's + * original digests during extraction — see + * backend/src/services/sapAssets.ts for both pin sets. + */ +export const SAP_ASSET_SPECS: SapAssetSpec[] = [ + { + name: "CommerceKit", + size: 3271840, + sha256: "b84ff12c21987856c0a17b78f1ad82b73195a6dec5f3b208a17d245555a2c8a2", + }, + { + name: "CommerceCore", + size: 115712, + sha256: "05707cd937798f2b5189471f513672ac6242ffbffc38f06ed2e4fb4345156819", + }, + { + name: "CoreFP", + size: 14904192, + sha256: "97c899f2fb076bdf7f810fe00ceb335d4af85efab0f2de737ad0aedd991c8277", + }, + { + name: "CoreFP.icxs", + size: 5288352, + sha256: "473e78af86979f5bd4f6269561caf770b3d16c098d918846eeac8cdd2fe6566a", + }, +]; + +const CACHE_NAME = "asspp-sap-assets-v1"; + +async function fetchWithProgress( + url: string, + onProgress?: (loaded: number, total: number) => void, +): Promise { + const response = await fetch(url, { headers: authHeaders() }); + if (!response.ok) { + throw new Error(`SAP asset download failed: HTTP ${response.status}`); + } + const total = Number(response.headers.get("content-length") ?? 0); + if (!response.body) { + const buffer = await response.arrayBuffer(); + return new Uint8Array(buffer); + } + const reader = response.body.getReader(); + const chunks: Uint8Array[] = []; + let loaded = 0; + for (;;) { + const { done, value } = await reader.read(); + if (done) { + break; + } + chunks.push(value); + loaded += value.length; + onProgress?.(loaded, total); + } + const assembled = new Uint8Array(loaded); + let offset = 0; + for (const chunk of chunks) { + assembled.set(chunk, offset); + offset += chunk.length; + } + return assembled; +} + +async function digestMatches(data: Uint8Array, expected: string): Promise { + const view = new Uint8Array(data.length); + view.set(data); + const digest = await crypto.subtle.digest("SHA-256", view.buffer as ArrayBuffer); + const actual = Array.from(new Uint8Array(digest)) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); + return actual === expected; +} + +/** + * Loads all four SAP assets, verifying digests. Served bytes come from the + * backend cache (public Apple data); the browser persists them in the Cache + * API so the ~38 MiB transfer happens once. + */ +export async function loadSapAssets( + onProgress?: (loadedBytes: number, totalBytes: number) => void, +): Promise { + const totalBytes = SAP_ASSET_SPECS.reduce((sum, spec) => sum + spec.size, 0); + let loadedBytes = 0; + let cache: Cache | null = null; + try { + cache = await caches.open(CACHE_NAME); + } catch { + // Cache API unavailable (private mode); fall back to direct downloads. + } + + const bundle: Record = {}; + for (const spec of SAP_ASSET_SPECS) { + let data: Uint8Array | null = null; + + if (cache) { + const cached = await cache.match(`/api/sap-assets/${spec.name}`); + if (cached) { + const buffer = await cached.arrayBuffer(); + const cachedBytes = new Uint8Array(buffer); + if (await digestMatches(cachedBytes, spec.sha256)) { + data = new Uint8Array(cachedBytes); + } + } + } + + if (!data) { + data = await fetchWithProgress(`/api/sap-assets/${spec.name}`, (loaded) => + onProgress?.(loadedBytes + loaded, totalBytes), + ); + if (!(await digestMatches(data, spec.sha256))) { + throw new Error(`SAP asset ${spec.name} failed integrity verification`); + } + if (cache) { + const copy = new Uint8Array(data); + await cache.put( + `/api/sap-assets/${spec.name}`, + new Response(copy.buffer as ArrayBuffer, { + headers: { "Content-Type": "application/octet-stream" }, + }), + ); + } + } + + loadedBytes += spec.size; + onProgress?.(loadedBytes, totalBytes); + bundle[spec.name] = data; + } + + return { + commerceKit: bundle["CommerceKit"], + commerceCore: bundle["CommerceCore"], + coreFP: bundle["CoreFP"], + coreFPICXS: bundle["CoreFP.icxs"], + }; +} + +/** Drops the cached assets (used after a digest pin update). */ +export async function clearSapAssets(): Promise { + try { + await caches.delete(CACHE_NAME); + } catch { + // ignore + } +} diff --git a/frontend/src/apple/sap/client.ts b/frontend/src/apple/sap/client.ts new file mode 100644 index 00000000..1ce62161 --- /dev/null +++ b/frontend/src/apple/sap/client.ts @@ -0,0 +1,163 @@ +// Main-thread SAP signer client: spawns the emulation worker, shuttles +// machine calls to it, and exposes the SapSigner orchestration used by the +// authentication flow. + +import { SapSigner, type SapMachineDriver } from "./signer"; +import { exchangeSetupBuffer, fetchSetupCertificate } from "./protocol"; +import type { SapSignerOptions } from "./types"; + +async function loadWorkerWasmBinary(): Promise { + const url = new URL("./vendor/unicorn.wasm", import.meta.url); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`SAP engine download failed: HTTP ${response.status}`); + } + return response.arrayBuffer(); +} + +interface WorkerResult { + type: "result"; + id: number; + [key: string]: unknown; +} + +interface WorkerError { + type: "error"; + id: number; + message: string; +} + +class WorkerMachineDriver implements SapMachineDriver { + private nextId = 1; + private readonly pending = new Map< + number, + { resolve: (value: WorkerResult) => void; reject: (error: Error) => void } + >(); + + constructor(private readonly worker: Worker) { + worker.onmessage = (event: MessageEvent) => { + const message = event.data; + const entry = this.pending.get(message.id); + if (!entry) { + return; + } + this.pending.delete(message.id); + if (message.type === "error") { + entry.reject(new Error(message.message)); + } else { + entry.resolve(message); + } + }; + } + + private call(request: Record): Promise { + const id = this.nextId++; + return new Promise((resolve, reject) => { + this.pending.set(id, { resolve, reject }); + this.worker.postMessage({ ...request, id }); + }); + } + + /** Loads the emulation runtime into the worker. */ + async open( + assets: { + commerceKit: Uint8Array; + commerceCore: Uint8Array; + coreFP: Uint8Array; + coreFPICXS: Uint8Array; + }, + wasmBinary: ArrayBuffer, + ): Promise { + await this.call({ + type: "open", + assets: { + commerceKit: assets.commerceKit.slice().buffer, + commerceCore: assets.commerceCore.slice().buffer, + coreFP: assets.coreFP.slice().buffer, + coreFPICXS: assets.coreFPICXS.slice().buffer, + }, + wasmBinary, + }); + } + + async initialize(hardwareID: Uint8Array): Promise { + const copy = hardwareID.slice(); + const result = await this.call({ + type: "initialize", + hardwareID: copy.buffer, + }); + return result.contextValue as number; + } + + async exchange( + version: number, + hardwareID: Uint8Array, + contextValue: number, + input: Uint8Array, + ): Promise<{ output: Uint8Array; state: number }> { + const hw = hardwareID.slice(); + const payload = input.slice(); + const result = await this.call({ + type: "exchange", + version, + hardwareID: hw.buffer, + contextValue, + input: payload.buffer, + }); + return { + output: new Uint8Array(result.output as ArrayBuffer), + state: result.state as number, + }; + } + + async sign(contextValue: number, input: Uint8Array): Promise { + const payload = input.slice(); + const result = await this.call({ + type: "sign", + contextValue, + input: payload.buffer, + }); + return new Uint8Array(result.signature as ArrayBuffer); + } + + async teardown(contextValue: number): Promise { + await this.call({ type: "teardown", contextValue }); + } + + async close(): Promise { + await this.call({ type: "close" }); + this.worker.terminate(); + } +} + +/** + * Creates a production signer: emulation runs in a dedicated worker, setup + * network calls flow through the wisp tunnel on the main thread. + */ +export async function createSapSigner( + options: SapSignerOptions, +): Promise { + const worker = new Worker( + new URL("./worker.ts", import.meta.url), + { type: "module" }, + ); + const driver = new WorkerMachineDriver(worker); + + try { + await driver.open( + options.assets, + options.wasmBinary ?? (await loadWorkerWasmBinary()), + ); + } catch (error) { + worker.terminate(); + throw error; + } + + return SapSigner.create(options, driver, { + fetchCertificate: () => fetchSetupCertificate(options), + exchange: (input) => exchangeSetupBuffer(options, input), + }).catch((error) => { + worker.terminate(); + throw error; + }); +} diff --git a/frontend/src/apple/sap/engine.ts b/frontend/src/apple/sap/engine.ts new file mode 100644 index 00000000..eefed4c0 --- /dev/null +++ b/frontend/src/apple/sap/engine.ts @@ -0,0 +1,289 @@ +// Unicorn 2.x WASM engine wrapper (x86_64 guest) for the SAP signer. +// +// The vendored emscripten build exposes a double-based glue API (see +// frontend/scripts/build-unicorn-wasm.sh): all 64-bit guest addresses cross +// the boundary as JS numbers, which is exact for integers below 2^53 — the +// SAP guest memory map stays below 2^48. + +// Unicorn x86_64 register IDs (unicorn/x86.h). +export const X86_REG = { + RAX: 35, + RCX: 38, + RDI: 39, + RDX: 40, + RIP: 41, + RSI: 43, + RSP: 44, + R8: 106, + R9: 107, +} as const; + +export const UC_ARCH_X86 = 4; +export const UC_MODE_64 = 8; + +interface UnicornGlueModule { + _uc2_open(arch: number, mode: number): number; + _uc2_close(uc: number): number; + _uc2_strerror(code: number): number; + _uc2_mem_map(uc: number, address: number, size: number): number; + _uc2_mem_unmap(uc: number, address: number, size: number): number; + _uc2_mem_write( + uc: number, + address: number, + bufferOffset: number, + length: number, + ): number; + _uc2_mem_read( + uc: number, + address: number, + bufferOffset: number, + length: number, + ): number; + _uc2_reg_write(uc: number, regid: number, value: number): number; + _uc2_reg_read(uc: number, regid: number): number; + _uc2_emu_start( + uc: number, + begin: number, + until: number, + timeoutUs: number, + count: number, + ): number; + _uc2_emu_stop(uc: number): number; + _uc2_hook_add_code(uc: number, begin: number, end: number): number; + _uc2_hook_del(uc: number, handle: number): number; + _uc2_set_code_hook_cb(fpIndex: number): void; + _uc2_scratch_alloc(length: number): number; + _uc2_scratch_free(offset: number): void; + addFunction(fn: (...args: unknown[]) => void, signature: string): number; + removeFunction(fpIndex: number): void; + UTF8ToString(ptr: number): string; + HEAPU8: Uint8Array; +} + +type UnicornFactory = ( + config?: Record, +) => Promise; + +let factoryPromise: Promise | null = null; + +async function loadFactory(): Promise { + if (!factoryPromise) { + factoryPromise = import("./vendor/unicorn.mjs").then( + (mod) => mod.default as UnicornFactory, + ); + } + return factoryPromise; +} + +function throwIfError(module: UnicornGlueModule, code: number, what: string) { + if (code === 0) { + return; + } + const messagePtr = module._uc2_strerror(code); + const detail = messagePtr ? module.UTF8ToString(messagePtr) : `code ${code}`; + throw new Error(`${what}: ${detail}`); +} + +export class UnicornEngine { + /** Set by the machine; invoked for every hooked address. */ + onCodeHook: ((address: number, size: number) => void) | null = null; + + private hookPointer = 0; + + private constructor( + private readonly module: UnicornGlueModule, + private readonly handle: number, + ) {} + + static async open( + options?: { wasmBinary?: ArrayBuffer }, + ): Promise { + const factory = await loadFactory(); + const config: Record = {}; + if (options?.wasmBinary) { + config.wasmBinary = options.wasmBinary; + } + const module = await factory(config); + + const handle = module._uc2_open(UC_ARCH_X86, UC_MODE_64); + if (handle === 0) { + throw new Error("uc_open failed"); + } + + return new UnicornEngine(module, handle); + } + + /** Install the engine-wide code hook callback and register its table slot. */ + attachCodeHook(): void { + if (this.hookPointer !== 0) { + return; + } + let engineRef: UnicornEngine | null = null; + this.hookPointer = this.module.addFunction( + ((address: number, size: number) => { + engineRef?.onCodeHook?.(address, size); + }) as unknown as (...args: unknown[]) => void, + "vdi", + ); + this.module._uc2_set_code_hook_cb(this.hookPointer); + engineRef = this; + } + + detachCodeHook(): void { + if (this.hookPointer === 0) { + return; + } + this.module.removeFunction(this.hookPointer); + this.hookPointer = 0; + } + + memMap(address: number, size: number): void { + throwIfError( + this.module, + this.module._uc2_mem_map(this.handle, address, size), + `mem_map(${address.toString(16)})`, + ); + } + + memUnmap(address: number, size: number): void { + throwIfError( + this.module, + this.module._uc2_mem_unmap(this.handle, address, size), + `mem_unmap(${address.toString(16)})`, + ); + } + + memWrite(address: number, data: Uint8Array): void { + if (data.length === 0) { + return; + } + const offset = this.module._uc2_scratch_alloc(data.length); + if (offset === 0) { + throw new Error("scratch alloc failed"); + } + try { + this.module.HEAPU8.set(data, offset); + throwIfError( + this.module, + this.module._uc2_mem_write( + this.handle, + address, + offset, + data.length, + ), + `mem_write(${address.toString(16)}, ${data.length})`, + ); + } finally { + this.module._uc2_scratch_free(offset); + } + } + + memRead(address: number, length: number): Uint8Array { + const output = new Uint8Array(length); + if (length === 0) { + return output; + } + const offset = this.module._uc2_scratch_alloc(length); + if (offset === 0) { + throw new Error("scratch alloc failed"); + } + try { + throwIfError( + this.module, + this.module._uc2_mem_read(this.handle, address, offset, length), + `mem_read(${address.toString(16)}, ${length})`, + ); + output.set(this.module.HEAPU8.subarray(offset, offset + length)); + return output; + } finally { + this.module._uc2_scratch_free(offset); + } + } + + regRead(register: number): number { + const value = this.module._uc2_reg_read(this.handle, register); + if (value === -1) { + throw new Error(`reg_read(${register}) failed`); + } + return value; + } + + regWrite(register: number, value: number): void { + throwIfError( + this.module, + this.module._uc2_reg_write(this.handle, register, value), + `reg_write(${register})`, + ); + } + + emuStart( + begin: number, + until: number, + timeoutUs: number, + count: number, + ): void { + throwIfError( + this.module, + this.module._uc2_emu_start(this.handle, begin, until, timeoutUs, count), + `emu_start(${begin.toString(16)}, ${until.toString(16)})`, + ); + } + + emuStop(): void { + throwIfError( + this.module, + this.module._uc2_emu_stop(this.handle), + "emu_stop", + ); + } + + /** Temporary diagnostic: report unmapped memory accesses from the guest. */ + onMemoryInvalid: + | ((type: number, address: number, size: number, value: number) => number) + | null = null; + + attachMemoryInvalidHook(): void { + const mod = this.module as unknown as { + _uc2_hook_add_mem_invalid(uc: number): number; + _uc2_set_mem_hook_cb(fp: number): void; + }; + if (!mod._uc2_hook_add_mem_invalid) { + return; + } + let engineRef: UnicornEngine | null = null; + const fp = this.module.addFunction( + ((type: number, address: number, size: number, value: number) => + engineRef?.onMemoryInvalid?.(type, address, size, value) ?? 0) as unknown as ( + ...args: unknown[] + ) => number, + "dddd", + ); + mod._uc2_set_mem_hook_cb(fp); + mod._uc2_hook_add_mem_invalid(this.handle); + engineRef = this; + } + + addCodeHook( + begin: number, + end: number, + ): number { + const handle = this.module._uc2_hook_add_code(this.handle, begin, end); + if (handle === 0) { + throw new Error("hook_add(UC_HOOK_CODE) failed"); + } + return handle; + } + + hookDel(handle: number): void { + throwIfError( + this.module, + this.module._uc2_hook_del(this.handle, handle), + "hook_del", + ); + } + + close(): void { + this.detachCodeHook(); + this.module._uc2_close(this.handle); + } +} diff --git a/frontend/src/apple/sap/machImage.ts b/frontend/src/apple/sap/machImage.ts new file mode 100644 index 00000000..3393101d --- /dev/null +++ b/frontend/src/apple/sap/machImage.ts @@ -0,0 +1,818 @@ +// Minimal Mach-O (x86_64) image parser/relocator for the SAP guest images. +// Ported from ipatool's internal/sap/machimage (which uses blacktop/go-macho). +// +// Supports exactly what the SAP guest needs: fat-binary slicing, LC_SEGMENT_64, +// LC_SYMTAB symbol lookup, and classic dyld_info rebase/bind/weak/lazy fixups. + +const MACHO_MAGIC_64 = 0xfeedfacf; +const FAT_MAGIC = 0xcafebabe; +const FAT_MAGIC_64 = 0xcafebabf; +const CPU_TYPE_X86_64 = 0x01000007; + +const LC_SYMTAB = 0x2; +const LC_SEGMENT_64 = 0x19; +const LC_DYLD_INFO = 0x22; +const LC_DYLD_INFO_ONLY = 0x80000022; + +const REBASE_TYPE_POINTER = 1; + +const BIND_TYPE_POINTER = 1; + +const PAGE_SIZE = 0x1000; +const MAX_IMAGE_SPAN = 1 << 30; +const POINTER_SIZE = 8; + +interface Segment { + name: string; + address: number; + size: number; + fileOff: number; + fileSize: number; +} + +export interface MachBind { + segment: string; + segOffset: number; + name: string; + type: number; + addend: number; +} + +interface MachRebase { + segment: string; + offset: number; + type: number; + value: number; +} + +interface Symbol { + name: string; + value: number; +} + +export interface GuestMemory { + memMap(address: number, size: number): void; + memWrite(address: number, data: Uint8Array): void; +} + +function readCString(view: DataView, offset: number): string { + let end = offset; + const limit = view.byteLength; + while (end < limit && view.getUint8(end) !== 0) { + end++; + } + const bytes = new Uint8Array( + view.buffer, + view.byteOffset + offset, + end - offset, + ); + return new TextDecoder("latin1").decode(bytes); +} + +function align(value: number, alignment: number): number { + // Math-based: JS bitwise ops are 32-bit and truncate SAP guest addresses. + return Math.floor((value + alignment - 1) / alignment) * alignment; +} + +class UlebReader { + private offset: number; + + constructor( + private readonly view: DataView, + start: number, + private readonly end: number, + ) { + this.offset = start; + } + + get position(): number { + return this.offset; + } + + get remaining(): number { + return this.end - this.offset; + } + + readUleb(): number { + // Returns the uleb128 value wrapped to uint64, as a JS number (exact up + // to 2^53; larger values keep only their low bits' magnitude and are only + // used for address arithmetic in segments < 2^48). Callers that must + // distinguish negative deltas (bind ADD_ADDR_ULEB) use readUlebBig. + return Number(this.readUlebBig()); + } + + readUlebBig(): bigint { + let result = 0n; + let shift = 0n; + for (;;) { + const byte = this.readByte(); + result |= BigInt(byte & 0x7f) << shift; + if ((byte & 0x80) === 0) { + return BigInt.asUintN(64, result); + } + shift += 7n; + if (shift > 63n) { + throw new Error("dyld uleb overflow"); + } + } + } + + readSleb(): number { + let result = 0; + let shift = 0; + for (;;) { + const byte = this.readByte(); + result |= (byte & 0x7f) << shift; + shift += 7; + if ((byte & 0x80) === 0) { + if (shift < 64 && (byte & 0x40) !== 0) { + result |= -1 << shift; + } + break; + } + if (shift > 63) { + throw new Error("dyld sleb overflow"); + } + } + return result; + } + + readCString(): string { + const bytes: number[] = []; + for (;;) { + const byte = this.readByte(); + if (byte === 0) { + break; + } + bytes.push(byte); + if (bytes.length > 4096) { + throw new Error("dyld symbol name exceeds 4096 bytes"); + } + } + return String.fromCharCode(...bytes); + } + + readByte(): number { + if (this.offset >= this.end) { + throw new Error("dyld opcode stream truncated"); + } + return this.view.getUint8(this.offset++); + } +} + +export class MachImage { + private base: number; + private segments: Segment[] = []; + private symbols: Map = new Map(); + private rebases: MachRebase[] = []; + private binds: MachBind[] = []; + private relocated = false; + private loadedBase = 0; + + private constructor( + public readonly name: string, + private readonly data: Uint8Array, + ) { + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + + const header = this.parseHeader(view); + this.base = header.base; + this.segments = header.segments; + this.parseSymtab(view, header.symtab); + this.rebases = this.parseRebases(view, header.dyldInfo); + this.binds = this.parseBinds(view, header.dyldInfo); + this.validateSegments(); + } + + static open(name: string, input: Uint8Array): MachImage { + return new MachImage(name, amd64Slice(input)); + } + + export(name: string, loadBase: number): number { + const address = this.symbols.get(name); + if (address === undefined) { + throw new Error(`find ${name} in ${this.name}: symbol not found`); + } + if (address < this.base) { + throw new Error( + `symbol ${name} in ${this.name} precedes image base`, + ); + } + return loadBase + (address - this.base); + } + + relocate( + loadBase: number, + resolve: (name: string) => number, + ): void { + if (this.relocated) { + throw new Error(`${this.name} is already relocated`); + } + + const view = new DataView( + this.data.buffer, + this.data.byteOffset, + this.data.byteLength, + ); + + for (const relocation of this.rebases) { + if (relocation.type !== REBASE_TYPE_POINTER) { + throw new Error( + `${this.name} uses unsupported rebase type ${relocation.type}`, + ); + } + if (relocation.value < this.base) { + throw new Error( + `${this.name} contains a rebase below its image base`, + ); + } + const offset = this.segmentFileOffsetOrBss( + relocation.segment, + relocation.offset, + POINTER_SIZE, + ); + if (offset < 0) { + continue; + } + const address = loadBase + (relocation.value - this.base); + this.putPointer(view, offset, address); + } + + for (const binding of this.binds) { + // Lazy bind streams may omit SET_TYPE; dyld's default is a pointer bind. + if (binding.type !== 0 && binding.type !== BIND_TYPE_POINTER) { + throw new Error( + `${this.name} uses unsupported bind type ${binding.type} for ${binding.name}`, + ); + } + const offset = this.segmentFileOffsetOrBss( + binding.segment, + binding.segOffset, + POINTER_SIZE, + ); + if (offset < 0) { + continue; + } + const resolved = resolve(binding.name); + this.putPointer(view, offset, resolved + binding.addend); + } + + this.relocated = true; + this.loadedBase = loadBase; + } + + /** Test-only: the relocated image payload (diffing hook). */ + debugBytes(): Uint8Array { + return this.data.slice(); + } + + load(memory: GuestMemory): void { + if (!this.relocated) { + throw new Error(`${this.name} must be relocated before loading`); + } + + let span = 0; + for (const segment of this.segments) { + if (segment.name === "__PAGEZERO" || segment.size === 0) { + continue; + } + if (segment.address < this.base) { + throw new Error( + `segment ${segment.name} in ${this.name} precedes image base`, + ); + } + const end = segment.address - this.base + segment.size; + if (end > MAX_IMAGE_SPAN) { + throw new Error( + `segment ${segment.name} makes ${this.name} too large`, + ); + } + span = Math.max(span, end); + } + + span = align(span, PAGE_SIZE); + if (span === 0) { + throw new Error(`${this.name} has no loadable segments`); + } + + memory.memMap(this.loadedBase, span); + + for (const segment of this.segments) { + if (segment.name === "__PAGEZERO" || segment.fileSize === 0) { + continue; + } + if (segment.fileOff + segment.fileSize > this.data.length) { + throw new Error( + `segment ${segment.name} data exceeds ${this.name}`, + ); + } + const address = this.loadedBase + (segment.address - this.base); + memory.memWrite( + address, + this.data.subarray( + segment.fileOff, + segment.fileOff + segment.fileSize, + ), + ); + } + } + + private parseHeader(view: DataView) { + const cputype = view.getInt32(4, true); + const ncmds = view.getUint32(16, true); + + const segments: Segment[] = []; + let symtab: { symoff: number; nsyms: number; stroff: number; strsize: number } | null = null; + let dyldInfo: { + rebaseOff: number; + rebaseSize: number; + bindOff: number; + bindSize: number; + weakOff: number; + weakSize: number; + lazyOff: number; + lazySize: number; + } | null = null; + + let offset = 32; + for (let index = 0; index < ncmds; index++) { + if (offset + 8 > view.byteLength) { + throw new Error(`load command ${index} exceeds ${this.name}`); + } + const cmd = view.getUint32(offset, true); + const cmdsize = view.getUint32(offset + 4, true); + if (cmdsize < 8 || offset + cmdsize > view.byteLength) { + throw new Error(`load command ${index} is malformed in ${this.name}`); + } + + if (cmd === LC_SEGMENT_64) { + const name = readCString(new DataView(view.buffer, view.byteOffset + offset + 8, 16), 0); + segments.push({ + name, + address: Number(view.getBigUint64(offset + 24, true)), + size: Number(view.getBigUint64(offset + 32, true)), + fileOff: Number(view.getBigUint64(offset + 40, true)), + fileSize: Number(view.getBigUint64(offset + 48, true)), + }); + } else if (cmd === LC_SYMTAB) { + symtab = { + symoff: view.getUint32(offset + 8, true), + nsyms: view.getUint32(offset + 12, true), + stroff: view.getUint32(offset + 16, true), + strsize: view.getUint32(offset + 20, true), + }; + } else if (cmd === LC_DYLD_INFO || cmd === LC_DYLD_INFO_ONLY) { + dyldInfo = { + rebaseOff: view.getUint32(offset + 8, true), + rebaseSize: view.getUint32(offset + 12, true), + bindOff: view.getUint32(offset + 16, true), + bindSize: view.getUint32(offset + 20, true), + weakOff: view.getUint32(offset + 24, true), + weakSize: view.getUint32(offset + 28, true), + lazyOff: view.getUint32(offset + 32, true), + lazySize: view.getUint32(offset + 36, true), + }; + } + + offset += cmdsize; + } + + if (cputype !== CPU_TYPE_X86_64) { + throw new Error( + `open ${this.name}: expected x86-64 Mach-O, found cputype ${cputype}`, + ); + } + + const loadable = segments.filter((s) => s.name !== "__PAGEZERO" && s.size > 0); + if (loadable.length === 0) { + throw new Error(`open ${this.name}: no loadable segments`); + } + const base = loadable.reduce( + (minimum, segment) => Math.min(minimum, segment.address), + Infinity, + ); + + return { base, segments, symtab, dyldInfo }; + } + + private parseSymtab( + view: DataView, + symtab: { symoff: number; nsyms: number; stroff: number; strsize: number } | null, + ): void { + if (!symtab) { + return; + } + const { symoff, nsyms, stroff, strsize } = symtab; + if (symoff + nsyms * 16 > view.byteLength) { + throw new Error(`symbol table exceeds ${this.name}`); + } + if (stroff + strsize > view.byteLength) { + throw new Error(`string table exceeds ${this.name}`); + } + + const strings = new DataView(view.buffer, view.byteOffset + stroff, strsize); + for (let index = 0; index < nsyms; index++) { + const entry = symoff + index * 16; + const strx = view.getUint32(entry, true); + const nType = view.getUint8(entry + 4); + const value = Number(view.getBigUint64(entry + 8, true)); + if (value === 0 || (nType & 0x0e) === 0) { + continue; + } + if (strx >= strsize) { + continue; + } + const name = readCString(strings, strx); + if (!name || this.symbols.has(name)) { + continue; + } + this.symbols.set(name, value); + } + } + + private parseRebases( + view: DataView, + info: { + rebaseOff: number; + rebaseSize: number; + bindOff: number; + bindSize: number; + weakOff: number; + weakSize: number; + lazyOff: number; + lazySize: number; + } | null, + ): MachRebase[] { + if (!info || info.rebaseSize === 0) { + return []; + } + + const reader = new UlebReader(view, info.rebaseOff, info.rebaseOff + info.rebaseSize); + const rebases: MachRebase[] = []; + + let type = 0; + let segment: Segment | null = null; + // Offsets move in uint64 space (negative ADD_ADDR_ULEB deltas are legal). + let offset = 0n; + + const readOriginalPointer = (): number => { + // The pre-rebase pointer value lives in the image itself; rebasing + // computes slide + original (go-macho/dyld semantics). + const fileOffset = this.requireCurrentSegmentFileOffset( + segment, + Number(offset), + ); + return Number(view.getBigUint64(fileOffset, true)); + }; + + const rebaseOnce = () => { + rebases.push({ + segment: segment!.name, + offset: Number(offset), + type, + value: readOriginalPointer(), + }); + }; + + while (reader.remaining > 0) { + const opcode = reader.readByte(); + const immediate = opcode & 0x0f; + const action = opcode & 0xf0; + + switch (action) { + case 0x00: // DONE + return rebases; + case 0x10: // SET_TYPE_IMM + type = immediate; + break; + case 0x20: { + // SET_SEGMENT_AND_OFFSET_ULEB + segment = this.requireSegment(immediate); + offset = reader.readUlebBig(); + break; + } + case 0x30: // ADD_ADDR_ULEB + offset = BigInt.asUintN(64, offset + reader.readUlebBig()); + break; + case 0x40: // ADD_ADDR_IMM_SCALED + offset = BigInt.asUintN(64, offset + BigInt(immediate * POINTER_SIZE)); + break; + case 0x50: { + // DO_REBASE_IMM_TIMES + for (let index = 0; index < immediate; index++) { + rebaseOnce(); + offset = BigInt.asUintN(64, offset + BigInt(POINTER_SIZE)); + } + break; + } + case 0x60: { + // DO_REBASE_ULEB_TIMES + const count = reader.readUleb(); + for (let index = 0; index < count; index++) { + rebaseOnce(); + offset = BigInt.asUintN(64, offset + BigInt(POINTER_SIZE)); + } + break; + } + case 0x70: { + // DO_REBASE_ADD_ADDR_ULEB + rebaseOnce(); + offset = BigInt.asUintN( + 64, + offset + reader.readUlebBig() + BigInt(POINTER_SIZE), + ); + break; + } + case 0x80: { + // DO_REBASE_ULEB_TIMES_SKIPPING_ULEB (skip is in bytes) + const count = reader.readUleb(); + const skip = reader.readUleb(); + for (let index = 0; index < count; index++) { + rebaseOnce(); + offset = BigInt.asUintN(64, offset + BigInt(skip + POINTER_SIZE)); + } + break; + } + default: + throw new Error(`unsupported rebase opcode ${opcode.toString(16)}`); + } + } + + return rebases; + } + + private parseBinds( + view: DataView, + info: { + rebaseOff: number; + rebaseSize: number; + bindOff: number; + bindSize: number; + weakOff: number; + weakSize: number; + lazyOff: number; + lazySize: number; + } | null, + ): MachBind[] { + if (!info) { + return []; + } + + const binds: MachBind[] = []; + this.parseBindStream(view, info.bindOff, info.bindOff + info.bindSize, binds); + this.parseBindStream(view, info.weakOff, info.weakOff + info.weakSize, binds); + this.parseBindStream( + view, + info.lazyOff, + info.lazyOff + info.lazySize, + binds, + true, + ); + + return binds; + } + + private parseBindStream( + view: DataView, + start: number, + end: number, + binds: MachBind[], + isLazy = false, + ): void { + const reader = new UlebReader(view, start, end); + + let type = 0; + let addend = 0; + let symbolName = ""; + let segment: Segment | null = null; + // Segment offsets move in uint64 space: ADD_ADDR_ULEB deltas are 64-bit + // encodings of negative steps, so all arithmetic stays in BigInt. + let segOffset = 0n; + + const bind = () => { + this.requireCurrentSegment(segment); + binds.push({ + segment: segment!.name, + segOffset: Number(segOffset), + name: symbolName, + type, + addend, + }); + }; + + while (reader.remaining > 0) { + const opcode = reader.readByte(); + const immediate = opcode & 0xf; + const action = opcode & 0xf0; + switch (action) { + case 0x00: // DONE + if (opcode === 0x00) { + if (!isLazy) { + return; + } + // Lazy streams terminate each entry with DONE and continue with + // the next; reset the per-entry state (go-macho/dyld semantics). + type = 0; + addend = 0; + symbolName = ""; + segment = null; + segOffset = 0n; + break; + } + break; + case 0x10: // SET_DYLIB_ORDINAL_IMM (ordinal unused: resolve by name) + break; + case 0x20: // SET_DYLIB_ORDINAL_ULEB + reader.readUleb(); + break; + case 0x30: // SET_DYLIB_SPECIAL_IMM + break; + case 0x40: // SET_SYMBOL_TRAILING_FLAGS_IMM + symbolName = reader.readCString(); + break; + case 0x50: // SET_TYPE_IMM + type = immediate; + break; + case 0x60: // SET_ADDEND_SLEB + addend = reader.readSleb(); + break; + case 0x70: { + // SET_SEGMENT_AND_OFFSET_ULEB + segment = this.requireSegment(immediate); + segOffset = reader.readUlebBig(); + break; + } + case 0x80: // ADD_ADDR_ULEB + segOffset = BigInt.asUintN(64, segOffset + reader.readUlebBig()); + break; + // NOTE: the bind opcode table has no ADD_ADDR_IMM_SCALED (that is a + // rebase-only opcode), so DO_BIND sits at 0x90, one slot below rebase. + case 0x90: // DO_BIND + bind(); + segOffset = BigInt.asUintN(64, segOffset + BigInt(POINTER_SIZE)); + break; + case 0xa0: // DO_BIND_ADD_ADDR_ULEB + bind(); + segOffset = BigInt.asUintN( + 64, + segOffset + reader.readUlebBig() + BigInt(POINTER_SIZE), + ); + break; + case 0xb0: // DO_BIND_ADD_ADDR_IMM_SCALED + bind(); + segOffset = BigInt.asUintN( + 64, + segOffset + BigInt(immediate * POINTER_SIZE + POINTER_SIZE), + ); + break; + case 0xc0: { + // DO_BIND_ULEB_TIMES_SKIPPING_ULEB + const count = reader.readUleb(); + const skip = reader.readUleb(); + for (let index = 0; index < count; index++) { + bind(); + segOffset = BigInt.asUintN( + 64, + segOffset + BigInt(skip + POINTER_SIZE), + ); + } + break; + } + case 0xd0: // BIND_OPCODE_THREADED (not present in 10.9-era images) + throw new Error( + `threaded bind opcodes are unsupported in ${this.name} (pos=0x${reader.position.toString(16)}, stream=[0x${start.toString(16)},0x${end.toString(16)}], segOffset=${segOffset}, symbol=${symbolName})`, + ); + default: + throw new Error(`unsupported bind opcode ${opcode.toString(16)}`); + } + } + } + + private requireSegment(index: number): Segment { + const segment = this.segments[index]; + if (!segment) { + throw new Error(`fixup references unknown segment index ${index} in ${this.name}`); + } + return segment; + } + + private requireCurrentSegment(segment: Segment | null): void { + if (!segment) { + throw new Error(`fixup in ${this.name} has no segment selected`); + } + } + + private requireCurrentSegmentFileOffset( + segment: Segment | null, + offset: number, + ): number { + this.requireCurrentSegment(segment); + return this.segmentFileOffset(segment!.name, offset, POINTER_SIZE); + } + + private validateSegments(): void { + for (const segment of this.segments) { + if (segment.fileSize > segment.size) { + throw new Error( + `segment ${segment.name} file data exceeds its memory size in ${this.name}`, + ); + } + if (segment.fileOff + segment.fileSize > this.data.length) { + throw new Error(`segment ${segment.name} data exceeds ${this.name}`); + } + } + } + + private segmentFileOffset(name: string, offset: number, size: number): number { + const result = this.segmentFileOffsetOrBss(name, offset, size); + if (result < 0) { + throw new Error( + `fixup at ${offset.toString(16)} lands in the BSS area of segment ${name} in ${this.name}`, + ); + } + return result; + } + + /** + * File offset for a fixup, or -1 when it targets the segment's BSS tail + * (within vmsize but past fileSize). dyld applies such fixups to the + * zero-filled memory at load time; for our purposes the loaded image is + * equally zero there, so callers skip them. + */ + private segmentFileOffsetOrBss( + name: string, + offset: number, + size: number, + ): number { + for (const segment of this.segments) { + if (segment.name !== name) { + continue; + } + if (offset + size > segment.size) { + throw new Error( + `fixup at ${offset.toString(16)} exceeds segment ${name} in ${this.name}`, + ); + } + if (offset + size > segment.fileSize) { + return -1; + } + const result = segment.fileOff + offset; + if (result + size > this.data.length) { + throw new Error(`fixup at ${result.toString(16)} exceeds ${this.name}`); + } + return result; + } + throw new Error(`fixup references unknown segment ${name} in ${this.name}`); + } + + private putPointer(view: DataView, offset: number, value: number): void { + if (offset + 8 > view.byteLength) { + throw new Error(`fixup at ${offset.toString(16)} exceeds ${this.name}`); + } + view.setUint32(offset, value % 4294967296, true); + view.setUint32(offset + 4, Math.floor(value / 4294967296), true); + } +} + + + +function amd64Slice(input: Uint8Array): Uint8Array { + const view = new DataView(input.buffer, input.byteOffset, input.byteLength); + if (input.length < 4) { + throw new Error("Mach-O input is too small"); + } + + const magic = view.getUint32(0, false); + if (magic !== FAT_MAGIC && magic !== FAT_MAGIC_64) { + return input; + } + + const wide = magic === FAT_MAGIC_64; + const count = view.getUint32(4, false); + const entrySize = wide ? 32 : 20; + if (4 + 8 + count * entrySize > view.byteLength) { + throw new Error("fat header exceeds input size"); + } + + for (let index = 0; index < count; index++) { + const entry = 8 + index * entrySize; + const cputype = view.getInt32(entry, false); + if (cputype !== CPU_TYPE_X86_64) { + continue; + } + // fat_arch(32): cputype, cpusubtype, offset, size, align (5 x u32). + // fat_arch_64: cputype, cpusubtype, offset(u64), size(u64), align, reserved. + const offset = wide + ? Number(view.getBigUint64(entry + 8, false)) + : view.getUint32(entry + 8, false); + const size = wide + ? Number(view.getBigUint64(entry + 16, false)) + : view.getUint32(entry + 12, false); + if (offset + size > input.length) { + throw new Error("x86-64 slice exceeds input size"); + } + return input.subarray(offset, offset + size); + } + + throw new Error("universal binary has no x86-64 slice"); +} diff --git a/frontend/src/apple/sap/machine.ts b/frontend/src/apple/sap/machine.ts new file mode 100644 index 00000000..1471f266 --- /dev/null +++ b/frontend/src/apple/sap/machine.ts @@ -0,0 +1,416 @@ +// SAP guest machine: maps the CommerceKit/CommerceCore/CoreFP images into an +// emulated x86_64 Unicorn instance and drives the obfuscated SAP entry points. +// Ported from ipatool's internal/sap/machine/machine.go. + +import { UnicornEngine, X86_REG } from "./engine"; +import { MachImage } from "./machImage"; +import { Shims, HEAP_BASE, HEAP_SIZE } from "./shims"; + +const SAP_GUEST_TIMEOUT_US = 60 * 1000 * 1000; // one minute, wall clock + +const RETURN_ADDRESS = 0x0000000100000000; +const CORE_FP_BASE = 0x0000100000000000; +const COMMERCE_BASE = 0x0000100040000000; +const KIT_BASE = 0x0000100080000000; +const SCRATCH_BASE = 0x0000300000000000; +const SCRATCH_SIZE = 32 << 20; +const STACK_BASE = 0x0000500000000000; +const STACK_SIZE = 8 << 20; +const STACK_END = STACK_BASE + STACK_SIZE; +const PAGE_SIZE = 0x1000; +const MAX_OUTPUT_SIZE = 16 << 20; + +const CORE_EXPORT_NAMES = [ + "_WIn9UJ86JKdV4dM", + "_X46O5IeS", + "_YlCJ3lg", + "_dku592fbFAj", + "_fdjkDSAFjklaf2s", + "_lxpgvVMLd0S7uRl", +]; + +const ENTRY_INITIALIZE = "_cp2g1b9ro"; +const ENTRY_EXCHANGE = "_Mib5yocT"; +const ENTRY_SIGN = "_Fc3vhtJDvr"; +const ENTRY_TEARDOWN = "_IPaI1oem5iL"; +const ENTRY_DISPOSE = "_jEHf8Xzsv8K"; + +export interface SapAssets { + commerceKit: Uint8Array; + commerceCore: Uint8Array; + coreFP: Uint8Array; + coreFPICXS: Uint8Array; +} + +interface EntryPoints { + initialize: number; + exchange: number; + sign: number; + teardown: number; + dispose: number; +} + +function align(value: number, alignment: number): number { + // Math-based: JS bitwise ops are 32-bit and truncate SAP guest addresses. + return Math.floor((value + alignment - 1) / alignment) * alignment; +} + +export function hardwareBlock(hardwareID: Uint8Array): Uint8Array { + if (hardwareID.length === 0 || hardwareID.length > 20) { + throw new Error("hardware ID must contain between 1 and 20 bytes"); + } + const result = new Uint8Array(24); + new DataView(result.buffer).setUint32(0, hardwareID.length, true); + result.set(hardwareID, 4); + return result; +} + +export class SapMachine { + private scratchCursor = 0; + private closed = false; + private readonly entry: EntryPoints; + private readonly services: Shims; + + private constructor( + private readonly engine: UnicornEngine, + entry: EntryPoints, + services: Shims, + ) { + this.entry = entry; + this.services = services; + } + + static async open( + assets: SapAssets, + options?: { wasmBinary?: ArrayBuffer }, + ): Promise { + const coreFP = MachImage.open("CoreFP", assets.coreFP); + const commerceCore = MachImage.open("CommerceCore", assets.commerceCore); + const commerceKit = MachImage.open("CommerceKit", assets.commerceKit); + + const exports = new Map(); + const coreExports: Record = {}; + + for (const name of CORE_EXPORT_NAMES) { + const address = coreFP.export(name, CORE_FP_BASE); + exports.set(name, address); + coreExports[name] = address; + } + + exports.set( + "_get_mac_address", + commerceCore.export("_get_mac_address", COMMERCE_BASE), + ); + + const entryNames = [ + ENTRY_INITIALIZE, + ENTRY_EXCHANGE, + ENTRY_SIGN, + ENTRY_TEARDOWN, + ENTRY_DISPOSE, + ]; + const resolved = new Map(); + for (const name of entryNames) { + const address = commerceKit.export(name, KIT_BASE); + exports.set(name, address); + resolved.set(name, address); + } + + const engine = await UnicornEngine.open(options); + + for (const region of [ + { address: RETURN_ADDRESS, size: PAGE_SIZE }, + { address: SCRATCH_BASE, size: SCRATCH_SIZE }, + { address: HEAP_BASE, size: HEAP_SIZE }, // guest heap (malloc shims) + { address: STACK_BASE, size: STACK_SIZE }, + ]) { + engine.memMap(region.address, region.size); + } + engine.memWrite(RETURN_ADDRESS, new Uint8Array([0xf4])); // HLT + + const services = await Shims.open(engine, coreExports, assets.coreFPICXS); + + + const resolver = (name: string): number => { + const address = exports.get(name); + if (address !== undefined) { + return address; + } + return services.resolve(name); + }; + + for (const item of [ + { name: "corefp", image: coreFP, base: CORE_FP_BASE }, + { name: "commercecore", image: commerceCore, base: COMMERCE_BASE }, + { name: "commercekit", image: commerceKit, base: KIT_BASE }, + ]) { + item.image.relocate(item.base, resolver); + item.image.load(engine); + } + + return new SapMachine( + engine, + { + initialize: resolved.get(ENTRY_INITIALIZE)!, + exchange: resolved.get(ENTRY_EXCHANGE)!, + sign: resolved.get(ENTRY_SIGN)!, + teardown: resolved.get(ENTRY_TEARDOWN)!, + dispose: resolved.get(ENTRY_DISPOSE)!, + }, + services, + ); + } + + initialize(hardwareID: Uint8Array): number { + const hardware = hardwareBlock(hardwareID); + this.beginCall(); + try { + const contextField = this.scratch(8); + const hardwareAddress = this.scratch(hardware); + const status = this.invoke(this.entry.initialize, [ + contextField, + hardwareAddress, + ]); + if (toInt32(status) !== 0) { + throw new Error(`SAP initialization returned ${toInt32(status)}`); + } + const contextValue = this.readUint64(contextField); + if (contextValue === 0) { + throw new Error("SAP initialization returned a null context"); + } + return contextValue; + } finally { + this.clearScratch(); + } + } + + exchange( + version: number, + hardwareID: Uint8Array, + contextValue: number, + input: Uint8Array, + ): { output: Uint8Array; state: number } { + const hardware = hardwareBlock(hardwareID); + this.beginCall(); + try { + const hardwareAddress = this.scratch(hardware); + const inputAddress = this.scratch(input); + const outputField = this.scratch(8); + const lengthField = this.scratch(8); + const resultField = this.scratch(4); + const status = this.invoke(this.entry.exchange, [ + version, + hardwareAddress, + contextValue, + inputAddress, + input.length, + outputField, + lengthField, + resultField, + ]); + if (toInt32(status) !== 0) { + throw new Error(`SAP exchange returned ${toInt32(status)}`); + } + const output = this.consumeOutput(outputField, lengthField); + const result = this.readUint32(resultField); + return { output, state: toInt32(result) }; + } finally { + this.clearScratch(); + } + } + + sign(contextValue: number, input: Uint8Array): Uint8Array { + this.beginCall(); + try { + const inputAddress = this.scratch(input); + const outputField = this.scratch(8); + const lengthField = this.scratch(8); + const status = this.invoke(this.entry.sign, [ + contextValue, + inputAddress, + input.length, + outputField, + lengthField, + ]); + if (toInt32(status) !== 0) { + throw new Error(`SAP signing returned ${toInt32(status)}`); + } + return this.consumeOutput(outputField, lengthField); + } finally { + this.clearScratch(); + } + } + + teardown(contextValue: number): void { + const status = this.invoke(this.entry.teardown, [contextValue]); + if (toInt32(status) !== 0) { + throw new Error(`SAP teardown returned ${toInt32(status)}`); + } + } + + close(): void { + if (this.closed) { + return; + } + this.closed = true; + this.services.close(); + this.engine.close(); + } + + private invoke(functionAddress: number, args: number[]): number { + if (this.closed) { + throw new Error("SAP guest machine is closed"); + } + if (functionAddress === 0) { + throw new Error("SAP guest entry point is unavailable"); + } + + const registers = [ + X86_REG.RDI, + X86_REG.RSI, + X86_REG.RDX, + X86_REG.RCX, + X86_REG.R8, + X86_REG.R9, + ]; + for (let index = 0; index < registers.length; index++) { + this.engine.regWrite(registers[index], args[index] ?? 0); + } + + const extra = Math.max(args.length - registers.length, 0); + let stackPointer = STACK_END - (extra + 1) * 8; + if (stackPointer % 16 !== 8) { + stackPointer -= 8; + } + + this.writeUint64(stackPointer, RETURN_ADDRESS); + for (let index = 0; index < extra; index++) { + this.writeUint64( + stackPointer + 8 + index * 8, + args[registers.length + index], + ); + } + this.engine.regWrite(X86_REG.RSP, stackPointer); + + this.services.resetFault(); + + try { + // SAP's cryptographic routines have input- and host-dependent instruction + // counts; bound execution by wall time rather than a fixed instruction cap. + this.engine.emuStart( + functionAddress, + RETURN_ADDRESS, + SAP_GUEST_TIMEOUT_US, + 0, + ); + } catch (error) { + if (this.services.fault) { + throw this.services.fault; + } + throw error; + } + + if (this.services.fault) { + throw this.services.fault; + } + + const instruction = this.engine.regRead(X86_REG.RIP); + if (instruction !== RETURN_ADDRESS) { + throw new Error( + `SAP guest stopped unexpectedly at ${instruction.toString(16)}`, + ); + } + + return this.engine.regRead(X86_REG.RAX); + } + + private beginCall(): void { + this.scratchCursor = 0; + } + + private scratch(data: Uint8Array | number): number { + const isData = typeof data !== "number"; + const size = isData ? (data as Uint8Array).length : data; + const reserved = align(Math.max(size, 1), 16); + if ( + this.scratchCursor > SCRATCH_SIZE || + reserved > SCRATCH_SIZE - this.scratchCursor + ) { + throw new Error("SAP guest scratch space exhausted"); + } + const address = SCRATCH_BASE + this.scratchCursor; + this.scratchCursor += reserved; + if (size !== 0) { + const bytes = isData ? (data as Uint8Array) : new Uint8Array(size); + if (bytes.length > size) { + throw new Error("scratch data exceeds reservation"); + } + this.engine.memWrite(address, bytes); + } + return address; + } + + private clearScratch(): void { + if (this.scratchCursor !== 0 && !this.closed) { + this.engine.memWrite( + SCRATCH_BASE, + new Uint8Array(this.scratchCursor), + ); + } + this.scratchCursor = 0; + } + + private consumeOutput(pointerField: number, lengthField: number): Uint8Array { + const pointer = this.readUint64(pointerField); + const length = this.readUint64(lengthField); + + let output: Uint8Array | null = null; + let outputError: Error | null = null; + + if (length > MAX_OUTPUT_SIZE) { + outputError = new Error( + `SAP output is ${length} bytes, maximum is ${MAX_OUTPUT_SIZE}`, + ); + } else if (length === 0) { + output = new Uint8Array(0); + } else if (pointer === 0) { + outputError = new Error("SAP returned a null output pointer"); + } else { + output = this.engine.memRead(pointer, length); + } + + if (pointer !== 0) { + const disposeStatus = this.invoke(this.entry.dispose, [pointer]); + if (toInt32(disposeStatus) !== 0 && !outputError) { + outputError = new Error(`SAP storage disposal returned ${toInt32(disposeStatus)}`); + } + } + + if (outputError) { + throw outputError; + } + return output!; + } + + private readUint32(address: number): number { + const data = this.engine.memRead(address, 4); + return new DataView(data.buffer, data.byteOffset).getUint32(0, true); + } + + private readUint64(address: number): number { + const data = this.engine.memRead(address, 8); + return Number( + new DataView(data.buffer, data.byteOffset).getBigUint64(0, true), + ); + } + + private writeUint64(address: number, value: number): void { + const data = new Uint8Array(8); + new DataView(data.buffer).setBigUint64(0, BigInt(value), true); + this.engine.memWrite(address, data); + } +} + +function toInt32(value: number): number { + return value | 0; +} diff --git a/frontend/src/apple/sap/protocol.ts b/frontend/src/apple/sap/protocol.ts new file mode 100644 index 00000000..948b2007 --- /dev/null +++ b/frontend/src/apple/sap/protocol.ts @@ -0,0 +1,62 @@ +// SAP setup protocol: fetches the Apple certificate and performs the key +// exchange. Both endpoints are public Apple services (no credentials); +// requests travel through the wisp tunnel via appleRequest like every other +// Apple API call. Ported from ipatool's internal/sap/protocol.go. + +import { appleRequest } from "../request"; +import { buildPlist, parsePlist } from "../plist"; +import type { SapEndpoints } from "./types"; + +const SETUP_CERTIFICATE_KEY = "sign-sap-setup-cert"; +const SETUP_BUFFER_KEY = "sign-sap-setup-buffer"; +const MAX_SETUP_BODY = 1 << 20; + +function plistBytes(document: string, key: string): Uint8Array { + const values = parsePlist(document) as Record; + const value = values[key]; + if (!(value instanceof Uint8Array) || value.length === 0) { + throw new Error(`Apple plist is missing ${key}`); + } + return value; +} + +export async function fetchSetupCertificate( + endpoints: SapEndpoints, +): Promise { + const response = await appleRequest({ + method: "GET", + host: new URL(endpoints.certificateURL).hostname, + path: `${new URL(endpoints.certificateURL).pathname}${new URL(endpoints.certificateURL).search}`, + }); + if (response.status !== 200) { + throw new Error(`SAP certificate request returned ${response.status}`); + } + if (response.body.length > MAX_SETUP_BODY) { + throw new Error("SAP certificate response exceeds 1 MiB"); + } + return plistBytes(response.body, SETUP_CERTIFICATE_KEY); +} + +export async function exchangeSetupBuffer( + endpoints: SapEndpoints, + input: Uint8Array, +): Promise { + const envelope = buildPlist({ [SETUP_BUFFER_KEY]: input }); + const url = new URL(endpoints.setupURL); + const response = await appleRequest({ + method: "POST", + host: url.hostname, + path: `${url.pathname}${url.search}`, + headers: { + "Content-Type": "application/x-plist", + }, + body: envelope, + }); + if (response.status !== 200) { + throw new Error(`SAP setup exchange returned ${response.status}`); + } + if (response.body.length > MAX_SETUP_BODY) { + throw new Error("SAP setup response exceeds 1 MiB"); + } + return plistBytes(response.body, SETUP_BUFFER_KEY); +} diff --git a/frontend/src/apple/sap/shims.ts b/frontend/src/apple/sap/shims.ts new file mode 100644 index 00000000..e85b3a70 --- /dev/null +++ b/frontend/src/apple/sap/shims.ts @@ -0,0 +1,712 @@ +// Guest service shims for the SAP machine: libc memory functions plus the +// CoreFoundation/IOKit/objc surface the emulated CommerceKit code touches. +// Ported from ipatool's internal/sap/machine (shim_memory.go, shim_platform.go, +// shims.go). Imports resolve to 16-byte stub slots holding a single RET; a code +// hook over the shim area dispatches on the entry address. + +import { UnicornEngine, X86_REG } from "./engine"; + +const SHIM_BASE = 0x0000200000000000; +const SHIM_CODE_SIZE = 0x80000; +const SHIM_SIZE = 0x100000; +const SHIM_SLOT_SIZE = 16; + +const MAX_GUEST_TRANSFER = 64 << 20; +const PAGE_SIZE = 0x1000; + +// machine.go memory layout (heap area is managed by the malloc shims). +export const HEAP_BASE = 0x0000400000000000; +export const HEAP_SIZE = 64 << 20; + +// Passed through regWrite as -1: wasm's saturating f64->i64 conversion turns +// -1 into the full 64-bit 0xFFFF...F (Go's math.MaxUint64), which cannot be +// represented exactly as a JS number. +const FAKE_HANDLE = -1; +const CORE_FP_FILE = 3; +const CORE_FP_PATH = "/System/Library/PrivateFrameworks/CoreFP.framework/CoreFP"; +const ICXS_PATH = "./../CoreFP.icxs"; +const KEY_SERIAL = "IOPlatformSerialNumber"; +const KEY_UUID = "IOPlatformUUID"; +const KEY_BOARD = "board-id"; +const KEYED_MESSAGE = "objectForKey:"; + +interface GuestAllocation { + size: number; + reserved: number; +} + +interface FreeBlock { + address: number; + size: number; +} + +type ShimHandler = () => void; + +function align(value: number, alignment: number): number { + // Math-based: JS bitwise ops are 32-bit and truncate SAP guest addresses. + return Math.floor((value + alignment - 1) / alignment) * alignment; +} + +export class Shims { + private readonly entries = new Map(); + private readonly handlers = new Map(); + readonly symbols = new Map(); + private codeCursor = SHIM_BASE; + private dataCursor = SHIM_BASE + SHIM_CODE_SIZE; + fault: Error | null = null; + private readonly coreExports: Map; + private readonly icxs: Uint8Array; + private icxsOffset = 0; + private errnoAddress = 0; + private heapCursor = 0; + private readonly allocations = new Map(); + private freeBlocks: FreeBlock[] = []; + private iterator = 0; + private hookHandle = 0; + + private constructor( + private readonly engine: UnicornEngine, + coreExports: Record, + icxs: Uint8Array, + ) { + this.coreExports = new Map(Object.entries(coreExports)); + this.icxs = icxs; + } + + static async open( + engine: UnicornEngine, + coreExports: Record, + icxs: Uint8Array, + ): Promise { + engine.memMap(SHIM_BASE, SHIM_SIZE); + + const shims = new Shims(engine, coreExports, icxs); + shims.registerMemoryServices(); + shims.registerPlatformServices(); + + engine.attachCodeHook(); + engine.onCodeHook = (address) => shims.dispatch(address); + shims.hookHandle = engine.addCodeHook( + SHIM_BASE, + SHIM_BASE + SHIM_CODE_SIZE - 1, + ); + + return shims; + } + + close(): void { + if (this.hookHandle) { + this.engine.hookDel(this.hookHandle); + } + } + + resolve(name: string): number { + const existing = this.symbols.get(name); + if (existing !== undefined) { + return existing; + } + return this.addFunction(name, () => { + throw new Error(`guest called unsupported import ${name}`); + }); + } + + private addAliases(names: string[], handler: ShimHandler): void { + for (const name of names) { + this.addFunction(name, handler); + } + } + + private addFunction(name: string, handler: ShimHandler): number { + const existing = this.symbols.get(name); + if (existing !== undefined) { + return existing; + } + if (this.codeCursor + SHIM_SLOT_SIZE > SHIM_BASE + SHIM_CODE_SIZE) { + throw new Error("guest service code area is full"); + } + + const address = this.codeCursor; + this.codeCursor += SHIM_SLOT_SIZE; + + this.engine.memWrite(address, new Uint8Array([0xc3])); // RET + + this.entries.set(address, name); + this.handlers.set(address, handler); + this.symbols.set(name, address); + return address; + } + + private addData(name: string, data: Uint8Array): number { + const existing = this.symbols.get(name); + if (existing !== undefined) { + return existing; + } + this.dataCursor = align(this.dataCursor, 8); + if (this.dataCursor + data.length > SHIM_BASE + SHIM_SIZE) { + throw new Error("guest service data area is full"); + } + const address = this.dataCursor; + this.dataCursor += Math.max(data.length, 8); + this.engine.memWrite(address, data); + this.symbols.set(name, address); + return address; + } + + dispatch(address: number): void { + const handler = this.handlers.get(address); + if (!handler) { + this.fail(new Error(`guest entered unknown service address ${address.toString(16)}`)); + return; + } + try { + handler(); + } catch (error) { + this.fail( + new Error( + `${this.entries.get(address) ?? "shim"}: ${error instanceof Error ? error.message : String(error)}`, + ), + ); + } + } + + private fail(error: Error): void { + if (!this.fault) { + this.fault = error; + } + try { + this.engine.emuStop(); + } catch { + // stopping an already-stopped engine is fine + } + } + + resetFault(): void { + this.fault = null; + } + + private argument(index: number): number { + const registers = [ + X86_REG.RDI, + X86_REG.RSI, + X86_REG.RDX, + X86_REG.RCX, + X86_REG.R8, + X86_REG.R9, + ]; + if (index >= 0 && index < registers.length) { + return this.engine.regRead(registers[index]); + } + if (index < 0) { + throw new Error("negative guest argument index"); + } + const stack = this.engine.regRead(X86_REG.RSP); + const data = this.engine.memRead(stack + 8 + (index - registers.length) * 8, 8); + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + return Number(view.getBigUint64(0, true)); + } + + private setResult(value: number): void { + this.engine.regWrite(X86_REG.RAX, value); + } + + private readUint32(address: number): number { + const data = this.engine.memRead(address, 4); + return new DataView(data.buffer, data.byteOffset).getUint32(0, true); + } + + private writeUint32(address: number, value: number): void { + const data = new Uint8Array(4); + new DataView(data.buffer).setUint32(0, value, true); + this.engine.memWrite(address, data); + } + + private readUint64(address: number): number { + const data = this.engine.memRead(address, 8); + return Number( + new DataView(data.buffer, data.byteOffset).getBigUint64(0, true), + ); + } + + private writeUint64(address: number, value: number): void { + const data = new Uint8Array(8); + new DataView(data.buffer).setBigUint64(0, BigInt(value), true); + this.engine.memWrite(address, data); + } + + readCString(address: number): string { + const maximum = 4096; + const value: number[] = []; + while (value.length < maximum) { + const item = this.engine.memRead(address + value.length, 1)[0]; + if (item === 0) { + return String.fromCharCode(...value); + } + value.push(item); + } + throw new Error(`guest string exceeds ${maximum} bytes`); + } + + private checkedSize(value: number): number { + if (value > MAX_GUEST_TRANSFER) { + throw new Error(`guest transfer size ${value} exceeds limit`); + } + return value; + } + + // ---- memory services ---- + + private registerMemoryServices(): void { + this.addAliases(["_malloc"], () => { + const size = this.argument(0); + this.setResult(this.allocate(size)); + }); + this.addAliases(["_malloc_good_size"], () => { + const size = this.argument(0); + this.setResult(align(Math.max(size, 1), 16)); + }); + this.addAliases(["_malloc_size"], () => { + const address = this.argument(0); + const allocation = this.allocations.get(address); + this.setResult(allocation ? allocation.reserved : 0); + }); + this.addAliases(["_calloc"], () => { + const count = this.argument(0); + const size = this.argument(1); + const total = count * size; + const address = this.allocate(total); + if (total !== 0) { + this.engine.memWrite(address, new Uint8Array(total)); + } + this.setResult(address); + }); + this.addAliases(["_realloc", "_reallocf"], () => { + this.realloc(); + }); + this.addAliases(["_free"], () => { + const address = this.argument(0); + if (address !== 0) { + this.release(address); + } + this.setResult(0); + }); + this.addAliases(["_memcpy", "_memmove"], () => { + this.memmove(); + }); + this.addAliases(["_memset"], () => { + this.memset(); + }); + this.addAliases(["___bzero"], () => { + this.bzero(); + }); + this.addAliases(["___memcpy_chk"], () => { + const length = this.argument(2); + const capacity = this.argument(3); + if (length > capacity) { + throw new Error("checked copy exceeds destination"); + } + this.memmove(); + }); + this.addAliases(["___memset_chk"], () => { + const length = this.argument(2); + const capacity = this.argument(3); + if (length > capacity) { + throw new Error("checked fill exceeds destination"); + } + this.memset(); + }); + this.addAliases(["_memcmp"], () => { + const left = this.argument(0); + const right = this.argument(1); + const length = this.checkedSize(this.argument(2)); + const a = this.engine.memRead(left, length); + const b = this.engine.memRead(right, length); + let order = 0; + for (let index = 0; index < length; index++) { + if (a[index] !== b[index]) { + order = a[index] < b[index] ? -1 : 1; + break; + } + } + this.setResult(order >>> 0); + }); + this.addAliases(["_strcmp"], () => { + const left = this.readCString(this.argument(0)); + const right = this.readCString(this.argument(1)); + this.setResult(left < right ? -1 : left > right ? 1 : 0); + }); + this.addAliases(["_strncmp"], () => { + this.strncmp(); + }); + this.addAliases(["_strlen"], () => { + const value = this.readCString(this.argument(0)); + this.setResult(value.length); + }); + } + + private allocate(size: number): number { + if (size > MAX_GUEST_TRANSFER) { + throw new Error(`allocation size ${size} exceeds limit`); + } + const reserved = align(Math.max(size, 1), 16); + for (let index = 0; index < this.freeBlocks.length; index++) { + const block = this.freeBlocks[index]; + if (block.size < reserved) { + continue; + } + const address = block.address; + if (block.size === reserved) { + this.freeBlocks.splice(index, 1); + } else { + block.address += reserved; + block.size -= reserved; + } + this.allocations.set(address, { size, reserved }); + return address; + } + if (this.heapCursor > HEAP_SIZE || reserved > HEAP_SIZE - this.heapCursor) { + throw new Error("guest heap exhausted"); + } + const address = HEAP_BASE + this.heapCursor; + this.heapCursor += reserved; + this.allocations.set(address, { size, reserved }); + return address; + } + + private release(address: number): void { + const allocation = this.allocations.get(address); + if (!allocation) { + throw new Error(`free unknown pointer ${address.toString(16)}`); + } + this.engine.memWrite(address, new Uint8Array(allocation.reserved)); + this.allocations.delete(address); + this.freeBlocks.push({ address, size: allocation.reserved }); + this.coalesceFreeBlocks(); + } + + private coalesceFreeBlocks(): void { + this.freeBlocks.sort((left, right) => left.address - right.address); + const merged: FreeBlock[] = []; + for (const block of this.freeBlocks) { + const last = merged[merged.length - 1]; + if (last && last.address + last.size === block.address) { + last.size += block.size; + continue; + } + merged.push({ ...block }); + } + this.freeBlocks = merged; + while (this.freeBlocks.length !== 0) { + const last = this.freeBlocks[this.freeBlocks.length - 1]; + if (last.address + last.size !== HEAP_BASE + this.heapCursor) { + break; + } + this.heapCursor -= last.size; + this.freeBlocks.pop(); + } + } + + private realloc(): void { + const oldAddress = this.argument(0); + const newSize = this.argument(1); + if (oldAddress === 0) { + this.setResult(this.allocate(newSize)); + return; + } + const oldAllocation = this.allocations.get(oldAddress); + if (!oldAllocation) { + throw new Error(`reallocate unknown pointer ${oldAddress.toString(16)}`); + } + if (newSize <= oldAllocation.reserved) { + oldAllocation.size = newSize; + this.setResult(oldAddress); + return; + } + const newAddress = this.allocate(newSize); + const data = this.engine.memRead(oldAddress, oldAllocation.size); + this.engine.memWrite(newAddress, data); + this.release(oldAddress); + this.setResult(newAddress); + } + + private memmove(): void { + const destination = this.argument(0); + const source = this.argument(1); + const length = this.checkedSize(this.argument(2)); + if (length !== 0) { + const data = this.engine.memRead(source, length); + this.engine.memWrite(destination, data); + } + this.setResult(destination); + } + + private memset(): void { + const destination = this.argument(0); + const value = this.argument(1); + const size = this.checkedSize(this.argument(2)); + this.engine.memWrite(destination, new Uint8Array(size).fill(value & 0xff)); + this.setResult(destination); + } + + private bzero(): void { + const destination = this.argument(0); + const size = this.checkedSize(this.argument(1)); + this.engine.memWrite(destination, new Uint8Array(size)); + this.setResult(destination); + } + + private strncmp(): void { + const left = this.argument(0); + const right = this.argument(1); + const length = this.checkedSize(this.argument(2)); + for (let offset = 0; offset < length; ) { + const leftAddress = left + offset; + const rightAddress = right + offset; + const chunk = Math.min( + length - offset, + PAGE_SIZE - (leftAddress % PAGE_SIZE), + PAGE_SIZE - (rightAddress % PAGE_SIZE), + ); + const a = this.engine.memRead(leftAddress, chunk); + const b = this.engine.memRead(rightAddress, chunk); + for (let index = 0; index < a.length; index++) { + if (a[index] !== b[index]) { + this.setResult((a[index] - b[index]) >>> 0); + return; + } + if (a[index] === 0) { + this.setResult(0); + return; + } + } + offset += chunk; + } + this.setResult(0); + } + + // ---- platform services ---- + + private registerPlatformServices(): void { + this.addAliases( + [ + "_CFBundleGetMainBundle", + "_CFDataGetBytePtr", + "_CFDataGetLength", + "_CFStringGetLength", + "_CFStringGetMaximumSizeForEncoding", + "_CFUUIDCreateString", + "_IORegistryEntryFromPath", + "_IORegistryEntrySearchCFProperty", + "_IOServiceMatching", + "_getenv", + "_pthread_self", + ], + () => this.setResult(0), + ); + this.addAliases( + [ + "_CFDictionaryGetValue", + "_DADiskCopyDescription", + "_DADiskCreateFromBSDName", + "_DASessionCreate", + "_IORegistryEntryCreateCFProperty", + ], + () => this.setResult(FAKE_HANDLE), + ); + this.addAliases( + [ + "_CFRelease", + "_IOObjectRelease", + "_close", + "_close$UNIX2003", + "_pthread_mutex_lock", + "_pthread_mutex_unlock", + "_pthread_rwlock_init", + "_pthread_rwlock_init$UNIX2003", + "_pthread_rwlock_unlock", + "_pthread_rwlock_unlock$UNIX2003", + "_pthread_rwlock_wrlock", + "_pthread_rwlock_wrlock$UNIX2003", + ], + () => this.setResult(0), + ); + this.addAliases(["_CFStringCreateWithCString"], () => { + const value = this.readCString(this.argument(1)); + this.setResult( + value === KEY_SERIAL || value === KEY_UUID || value === KEY_BOARD + ? FAKE_HANDLE + : 0, + ); + }); + this.addAliases(["_CFStringCreateWithCStringNoCopy"], () => { + this.setResult(0); + }); + this.addAliases(["_CFStringGetCString"], () => { + const buffer = this.argument(1); + const capacity = this.argument(2); + if (buffer === 0 || capacity === 0) { + this.setResult(0); + return; + } + this.engine.memWrite(buffer, new Uint8Array([0])); + this.setResult(1); + }); + this.addAliases(["_IOIteratorNext"], () => { + this.iterator = (this.iterator + 1) >>> 0; + this.setResult(this.iterator % 2); + }); + this.addAliases(["_IORegistryEntryGetParentEntry"], () => { + const parent = this.argument(2); + if (parent === 0) { + throw new Error("parent registry entry output is null"); + } + this.writeUint32(parent, 0xffffffff); + this.setResult(0); + }); + this.addAliases(["_IOServiceGetMatchingServices"], () => { + const iterator = this.argument(2); + if (iterator === 0) { + throw new Error("matching services iterator output is null"); + } + this.iterator = 0; + this.writeUint32(iterator, 0xffffffff); + this.setResult(0); + }); + this.addAliases(["_IOServiceGetMatchingService"], () => { + this.setResult(0xffffffff); // uc returns uint32 max per Go reference + }); + this.addAliases(["_OSAtomicCompareAndSwap32Barrier"], () => { + const oldValue = this.argument(0); + const newValue = this.argument(1); + const address = this.argument(2); + const current = this.readUint32(address); + if (current !== oldValue) { + this.setResult(0); + return; + } + this.writeUint32(address, newValue >>> 0); + this.setResult(1); + }); + this.addAliases(["___error"], () => { + this.setResult(this.errnoAddress); + }); + this.addAliases(["_abort", "___stack_chk_fail", "dyld_stub_binder"], () => { + throw new Error("guest aborted"); + }); + this.addAliases(["_arc4random"], () => { + const value = new Uint32Array(1); + crypto.getRandomValues(value); + this.setResult(value[0]); + }); + this.addAliases(["_dlopen"], () => { + const path = this.readCString(this.argument(0)); + this.setResult(path === CORE_FP_PATH ? FAKE_HANDLE : 0); + }); + this.addAliases(["_dlsym"], () => { + const name = this.readCString(this.argument(1)); + this.setResult(this.coreExports.get(`_${name}`) ?? 0); + }); + this.addAliases( + ["_fcntl", "_fcntl$UNIX2003", "_lstat$INODE64", "_statfs", "_statfs$INODE64"], + () => this.setResult(-1), // 64-bit -1 (see FAKE_HANDLE note) + ); + this.addAliases(["_gettimeofday"], () => { + this.gettimeofday(); + }); + this.addAliases(["_objc_msgSend"], () => { + const selector = this.readCString(this.argument(1)); + this.setResult(selector === KEYED_MESSAGE ? FAKE_HANDLE : 0); + }); + this.addAliases(["_open", "_open$UNIX2003"], () => { + const path = this.readCString(this.argument(0)); + if (path === ICXS_PATH) { + this.icxsOffset = 0; + this.setResult(CORE_FP_FILE); + return; + } + this.setResult(-1); // open() returns int -1 + }); + this.addAliases(["_pthread_once"], () => { + this.pthreadOnce(); + }); + this.addAliases(["_read", "_read$UNIX2003"], () => { + this.read(); + }); + this.addAliases(["_sysctl"], () => { + this.setResult(-1); // 64-bit -1 + }); + this.addAliases(["_sysctlbyname"], () => { + const lengthAddress = this.argument(2); + if (lengthAddress !== 0) { + this.writeUint64(lengthAddress, 0); + } + this.setResult(0); + }); + + this.errnoAddress = this.addData("guest.errno", new Uint8Array(8)); + this.addData( + "___stack_chk_guard", + new Uint8Array([0xa5, 0x71, 0x3c, 0xd9, 0x86, 0x42, 0xef, 0x10]), + ); + for (const name of [ + "_kCFAllocatorDefault", + "_kCFAllocatorNull", + "_kDADiskDescriptionVolumeUUIDKey", + "_kIOMasterPortDefault", + ]) { + this.addData(name, new Uint8Array(8)); + } + } + + private gettimeofday(): void { + const timeAddress = this.argument(0); + const zoneAddress = this.argument(1); + const now = Date.now(); + if (timeAddress !== 0) { + const data = new Uint8Array(16); + const view = new DataView(data.buffer); + view.setBigUint64(0, BigInt(Math.floor(now / 1000)), true); + view.setUint32(8, (now % 1000) * 1000, true); + this.engine.memWrite(timeAddress, data); + } + if (zoneAddress !== 0) { + this.engine.memWrite(zoneAddress, new Uint8Array(8)); + } + this.setResult(0); + } + + private pthreadOnce(): void { + const control = this.argument(0); + const initializer = this.argument(1); + const value = this.readUint64(control); + if (value === 0) { + this.setResult(0); + return; + } + this.writeUint64(control, 0); + const stack = this.engine.regRead(X86_REG.RSP) - 8; + this.writeUint64(stack, initializer); + this.engine.regWrite(X86_REG.RSP, stack); + this.setResult(0); + } + + private read(): void { + const descriptor = this.argument(0); + const buffer = this.argument(1); + const requested = this.argument(2); + if (descriptor !== CORE_FP_FILE) { + this.setResult(-1); // 64-bit -1 + return; + } + const size = this.checkedSize(requested); + const remaining = this.icxs.length - this.icxsOffset; + const count = Math.min(size, remaining); + if (count !== 0) { + this.engine.memWrite( + buffer, + this.icxs.subarray(this.icxsOffset, this.icxsOffset + count), + ); + this.icxsOffset += count; + } + this.setResult(count); + } +} diff --git a/frontend/src/apple/sap/signer.ts b/frontend/src/apple/sap/signer.ts new file mode 100644 index 00000000..f53b09fe --- /dev/null +++ b/frontend/src/apple/sap/signer.ts @@ -0,0 +1,118 @@ +// SAP signer: sequences the emulated CommerceKit entry points through the +// Apple setup key exchange and per-request signing. The machine driver can be +// the in-process SapMachine (tests) or a Web Worker proxy (production); setup +// network calls run on the caller's thread so they ride the wisp tunnel. + +import { + validateSapSignerOptions, + type SapSignerOptions, +} from "./types"; + +export interface SapMachineDriver { + initialize(hardwareID: Uint8Array): Promise; + exchange( + version: number, + hardwareID: Uint8Array, + contextValue: number, + input: Uint8Array, + ): Promise<{ output: Uint8Array; state: number }>; + sign(contextValue: number, input: Uint8Array): Promise; + teardown(contextValue: number): Promise; + close(): Promise; +} + +export interface SapSignerNetwork { + fetchCertificate: () => Promise; + exchange: (input: Uint8Array) => Promise; +} + +export class SapSigner { + private closed = false; + + private constructor( + private readonly driver: SapMachineDriver, + private readonly context: number, + ) {} + + /** + * Completes the Apple key exchange: initialize -> GET certificate -> + * exchange(state 1) -> POST setup -> exchange(state 0). + */ + static async create( + options: SapSignerOptions, + driver: SapMachineDriver, + network: SapSignerNetwork, + ): Promise { + validateSapSignerOptions(options); + + let context = 0; + try { + context = await driver.initialize(options.hardwareID); + + const certificate = await network.fetchCertificate(); + const first = await driver.exchange( + options.version, + options.hardwareID, + context, + certificate, + ); + if (first.state !== 1) { + throw new Error(`SAP setup entered unexpected state ${first.state}`); + } + if (first.output.length === 0) { + throw new Error("SAP setup message is empty"); + } + + const reply = await network.exchange(first.output); + const second = await driver.exchange( + options.version, + options.hardwareID, + context, + reply, + ); + if (second.state !== 0) { + throw new Error( + `SAP setup completed in unexpected state ${second.state}`, + ); + } + } catch (error) { + await driver.close().catch(() => undefined); + throw error; + } + + return new SapSigner(driver, context); + } + + /** Signs request-body bytes; returns the X-Apple-ActionSignature value. */ + async sign(input: Uint8Array): Promise { + if (this.closed) { + throw new Error("SAP signer is closed"); + } + const signature = await this.driver.sign(this.context, input); + if (signature.length === 0) { + throw new Error("SAP signing produced an empty signature"); + } + return bytesToBase64(signature); + } + + async close(): Promise { + if (this.closed) { + return; + } + this.closed = true; + try { + await this.driver.teardown(this.context); + } finally { + await this.driver.close(); + } + } +} + +function bytesToBase64(bytes: Uint8Array): string { + let binary = ""; + const chunk = 0x8000; + for (let offset = 0; offset < bytes.length; offset += chunk) { + binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk)); + } + return btoa(binary); +} diff --git a/frontend/src/apple/sap/types.ts b/frontend/src/apple/sap/types.ts new file mode 100644 index 00000000..f1ec862a --- /dev/null +++ b/frontend/src/apple/sap/types.ts @@ -0,0 +1,42 @@ +// Shared SAP types. + +export interface SapEndpoints { + /** GET endpoint returning the Apple certificate plist (bag: sign-sap-setup-cert). */ + certificateURL: string; + /** POST endpoint for the setup key exchange (bag: sign-sap-setup). */ + setupURL: string; + /** Protocol version from the bag (sign-sap-version); only 200 is supported. */ + version: number; +} + +export const SUPPORTED_SAP_VERSION = 200; + +export interface SapAssetBundle { + commerceKit: Uint8Array; + commerceCore: Uint8Array; + coreFP: Uint8Array; + coreFPICXS: Uint8Array; +} + +export interface SapSignerOptions extends SapEndpoints { + /** Per-account device identifier bytes (ASCII, 1..20 bytes). */ + hardwareID: Uint8Array; + assets: SapAssetBundle; + wasmBinary?: ArrayBuffer; +} + +/** Endpoint/hardware validation matching the Swift reference. */ +export function validateSapSignerOptions(options: SapSignerOptions): void { + if (options.version !== SUPPORTED_SAP_VERSION) { + throw new Error(`unsupported SAP version ${options.version}`); + } + if (options.hardwareID.length === 0 || options.hardwareID.length > 20) { + throw new Error("SAP hardware ID must contain between 1 and 20 bytes"); + } + for (const url of [options.certificateURL, options.setupURL]) { + const parsed = new URL(url); + if (parsed.protocol !== "https:" || !parsed.hostname || parsed.username) { + throw new Error(`SAP endpoint must be an absolute HTTPS URL: ${url}`); + } + } +} diff --git a/frontend/src/apple/sap/vendor/unicorn.d.mts b/frontend/src/apple/sap/vendor/unicorn.d.mts new file mode 100644 index 00000000..e14e3b45 --- /dev/null +++ b/frontend/src/apple/sap/vendor/unicorn.d.mts @@ -0,0 +1,5 @@ +// Type shim for the emscripten module built by scripts/unicorn-wasm/build.sh. +declare const UnicornModuleFactory: ( + config?: Record, +) => Promise; +export default UnicornModuleFactory; diff --git a/frontend/src/apple/sap/vendor/unicorn.mjs b/frontend/src/apple/sap/vendor/unicorn.mjs new file mode 100644 index 00000000..2d28ccd8 --- /dev/null +++ b/frontend/src/apple/sap/vendor/unicorn.mjs @@ -0,0 +1,16 @@ + +var UnicornModule = (() => { + var _scriptName = import.meta.url; + + return ( +async function(moduleArg = {}) { + var moduleRtn; + +var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){const{createRequire:createRequire}=await import("module");var require=createRequire(import.meta.url)}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=require("url").fileURLToPath(new URL("./",import.meta.url));read_=(filename,binary)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)onerror(err);else onload(binary?data.buffer:data)})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))}).then(onload,onerror)}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);var isFileURI=filename=>filename.startsWith("file://");function findWasmBinary(){if(Module["locateFile"]){var f="unicorn.wasm";if(!isDataURI(f)){return locateFile(f)}return f}return new URL("unicorn.wasm",import.meta.url).href}var wasmBinaryFile;function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){if(!wasmBinary){return new Promise((resolve,reject)=>{readAsync(binaryFile,response=>resolve(new Uint8Array(response)),error=>{try{resolve(getBinarySync(binaryFile))}catch(e){reject(e)}})})}return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}return instantiateArrayBuffer(binaryFile,imports,callback)}function getWasmImports(){return{a:wasmImports}}function createWasm(){var info=getWasmImports();function receiveInstance(instance,module){wasmExports=instance.exports;wasmExports=applySignatureConversions(wasmExports);wasmMemory=wasmExports["R"];updateMemoryViews();wasmTable=wasmExports["na"];addOnInit(wasmExports["S"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);readyPromiseReject(e)}}if(!wasmBinaryFile)wasmBinaryFile=findWasmBinary();instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function ExitStatus(status){this.name="ExitStatus";this.message=`Program terminated with exit(${status})`;this.status=status}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr>>>0];case"i8":return HEAP8[ptr>>>0];case"i16":return HEAP16[ptr>>>1>>>0];case"i32":return HEAP32[ptr>>>2>>>0];case"i64":abort("to do getValue(i64) use WASM_BIGINT");case"float":return HEAPF32[ptr>>>2>>>0];case"double":return HEAPF64[ptr>>>3>>>0];case"*":return HEAPU32[ptr>>>2>>>0];default:abort(`invalid type for getValue: ${type}`)}}var noExitRuntime=Module["noExitRuntime"]||true;function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr>>>0]=value;break;case"i8":HEAP8[ptr>>>0]=value;break;case"i16":HEAP16[ptr>>>1>>>0]=value;break;case"i32":HEAP32[ptr>>>2>>>0]=value;break;case"i64":abort("to do setValue(i64) use WASM_BIGINT");case"float":HEAPF32[ptr>>>2>>>0]=value;break;case"double":HEAPF64[ptr>>>3>>>0]=value;break;case"*":HEAPU32[ptr>>>2>>>0]=value;break;default:abort(`invalid type for setValue: ${type}`)}}var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();function syscallGetVarargI(){var ret=HEAP32[+SYSCALLS.varargs>>>2>>>0];SYSCALLS.varargs+=4;return ret}var syscallGetVarargP=syscallGetVarargI;var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>crypto.getRandomValues(view)}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");var randomFillSync=crypto_module["randomFillSync"];if(randomFillSync){return view=>crypto_module["randomFillSync"](view)}var randomBytes=crypto_module["randomBytes"];return view=>(view.set(randomBytes(view.byteLength)),view)}catch(e){}}abort("initRandomDevice")};var randomFill=view=>(randomFill=initRandomFill())(view);var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{idx>>>=0;var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var FS_stdin_getChar_buffer=[];var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{outIdx>>>=0;if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++>>>0]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++>>>0]=192|u>>6;heap[outIdx++>>>0]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++>>>0]=224|u>>12;heap[outIdx++>>>0]=128|u>>6&63;heap[outIdx++>>>0]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++>>>0]=240|u>>18;heap[outIdx++>>>0]=128|u>>12&63;heap[outIdx++>>>0]=128|u>>6&63;heap[outIdx++>>>0]=128|u&63}}heap[outIdx>>>0]=0;return outIdx-startIdx};function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};var zeroMemory=(address,size)=>{HEAPU8.fill(0,address,address+size);return address};var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var mmapAlloc=size=>{size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(!ptr)return 0;return zeroMemory(ptr,size)};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){throw FS.genericErrors[44]},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp},unlink(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir(node){var entries=[".",".."];for(var key of Object.keys(node.contents)){entries.push(key)}return entries},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length>>0)}return{ptr:ptr,allocated:allocated}},msync(stream,buffer,offset,length,mmapFlags){MEMFS.stream_ops.write(stream,buffer,0,length,offset,false);return 0}}};var asyncLoad=(url,onload,onerror,noRunDep)=>{var dep=!noRunDep?getUniqueRunDependency(`al ${url}`):"";readAsync(url,arrayBuffer=>{onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw`Loading data file "${url}" failed.`}});if(dep)addRunDependency(dep)};var FS_createDataFile=(parent,name,fileData,canRead,canWrite,canOwn)=>{FS.createDataFile(parent,name,fileData,canRead,canWrite,canOwn)};var preloadPlugins=Module["preloadPlugins"]||[];var FS_handledByPreloadPlugin=(byteArray,fullname,finish,onerror)=>{if(typeof Browser!="undefined")Browser.init();var handled=false;preloadPlugins.forEach(plugin=>{if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}onload?.();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{onerror?.();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,processData,onerror)}else{processData(url)}};var FS_modeStringToFlags=str=>{var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:class{constructor(errno){this.name="ErrnoError";this.errno=errno}},genericErrors:{},filesystems:null,syncFSRequests:0,FSStream:class{constructor(){this.shared={}}get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev;this.readMode=292|73;this.writeMode=146}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],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){if(typeof populate=="function"){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){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount(type,opts,mountpoint){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;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:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;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)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var i=0;iFS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams(){if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""});FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS:MEMFS}},init(input,output,error){FS.init.initialized=true;Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit(){FS.init.initialized=false;for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);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))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;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}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var UTF8ToString=(ptr,maxBytesToRead)=>{ptr>>>=0;return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat(func,path,buf){var stat=func(path);HEAP32[buf>>>2>>>0]=stat.dev;HEAP32[buf+4>>>2>>>0]=stat.mode;HEAPU32[buf+8>>>2>>>0]=stat.nlink;HEAP32[buf+12>>>2>>>0]=stat.uid;HEAP32[buf+16>>>2>>>0]=stat.gid;HEAP32[buf+20>>>2>>>0]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+24>>>2>>>0]=tempI64[0],HEAP32[buf+28>>>2>>>0]=tempI64[1];HEAP32[buf+32>>>2>>>0]=4096;HEAP32[buf+36>>>2>>>0]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();tempI64=[Math.floor(atime/1e3)>>>0,(tempDouble=Math.floor(atime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>>2>>>0]=tempI64[0],HEAP32[buf+44>>>2>>>0]=tempI64[1];HEAPU32[buf+48>>>2>>>0]=atime%1e3*1e3;tempI64=[Math.floor(mtime/1e3)>>>0,(tempDouble=Math.floor(mtime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>>2>>>0]=tempI64[0],HEAP32[buf+60>>>2>>>0]=tempI64[1];HEAPU32[buf+64>>>2>>>0]=mtime%1e3*1e3;tempI64=[Math.floor(ctime/1e3)>>>0,(tempDouble=Math.floor(ctime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>>2>>>0]=tempI64[0],HEAP32[buf+76>>>2>>>0]=tempI64[1];HEAPU32[buf+80>>>2>>>0]=ctime%1e3*1e3;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>>2>>>0]=tempI64[0],HEAP32[buf+92>>>2>>>0]=tempI64[1];return 0},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};var convertI32PairToI53Checked=(lo,hi)=>hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN;function ___syscall_fcntl64(fd,cmd,varargs){varargs>>>=0;SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();stream.flags|=arg;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;HEAP16[arg+offset>>>1>>>0]=2;return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){varargs>>>=0;SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();HEAP32[argp>>>2>>>0]=termios.c_iflag||0;HEAP32[argp+4>>>2>>>0]=termios.c_oflag||0;HEAP32[argp+8>>>2>>>0]=termios.c_cflag||0;HEAP32[argp+12>>>2>>>0]=termios.c_lflag||0;for(var i=0;i<32;i++){HEAP8[argp+i+17>>>0]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=HEAP32[argp>>>2>>>0];var c_oflag=HEAP32[argp+4>>>2>>>0];var c_cflag=HEAP32[argp+8>>>2>>>0];var c_lflag=HEAP32[argp+12>>>2>>>0];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(HEAP8[argp+i+17>>>0])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag:c_iflag,c_oflag:c_oflag,c_cflag:c_cflag,c_lflag:c_lflag,c_cc:c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();HEAP32[argp>>>2>>>0]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();HEAP16[argp>>>1>>>0]=winsize[0];HEAP16[argp+2>>>1>>>0]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){path>>>=0;varargs>>>=0;SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __abort_js=()=>{abort("")};var nowIsMonotonic=1;var __emscripten_get_now_is_monotonic=()=>nowIsMonotonic;function __emscripten_memcpy_js(dest,src,num){dest>>>=0;src>>>=0;num>>>=0;return HEAPU8.copyWithin(dest>>>0,src>>>0,src+num>>>0)}var __emscripten_throw_longjmp=()=>{throw Infinity};function __munmap_js(addr,len,prot,flags,fd,offset_low,offset_high){addr>>>=0;len>>>=0;var offset=convertI32PairToI53Checked(offset_low,offset_high);try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var _emscripten_date_now=()=>Date.now();var _emscripten_get_now;_emscripten_get_now=()=>performance.now();var getHeapMax=()=>4294901760;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};function _emscripten_resize_heap(requestedSize){requestedSize>>>=0;var oldSize=HEAPU8.length;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false}var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var stringToAscii=(str,buffer)=>{for(var i=0;i>>0]=str.charCodeAt(i)}HEAP8[buffer>>>0]=0};var _environ_get=function(__environ,environ_buf){__environ>>>=0;environ_buf>>>=0;var bufSize=0;getEnvStrings().forEach((string,i)=>{var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>>2>>>0]=ptr;stringToAscii(string,ptr);bufSize+=string.length+1});return 0};var _environ_sizes_get=function(penviron_count,penviron_buf_size){penviron_count>>>=0;penviron_buf_size>>>=0;var strings=getEnvStrings();HEAPU32[penviron_count>>>2>>>0]=strings.length;var bufSize=0;strings.forEach(string=>bufSize+=string.length+1);HEAPU32[penviron_buf_size>>>2>>>0]=bufSize;return 0};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>>2>>>0];var len=HEAPU32[iov+4>>>2>>>0];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>>=0;iovcnt>>>=0;pnum>>>=0;try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt);HEAPU32[pnum>>>2>>>0]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){var offset=convertI32PairToI53Checked(offset_low,offset_high);newOffset>>>=0;try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>>2>>>0]=tempI64[0],HEAP32[newOffset+4>>>2>>>0]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>>2>>>0];var len=HEAPU32[iov+4>>>2>>>0];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!="undefined"){offset+=curr}}return ret};function _fd_write(fd,iov,iovcnt,pnum){iov>>>=0;iovcnt>>>=0;pnum>>>=0;try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>>2>>>0]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var wasmTableMirror=[];var wasmTable;var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var uleb128Encode=(n,target)=>{if(n<128){target.push(n)}else{target.push(n%128|128,n>>7)}};var sigToWasmTypes=sig=>{var typeNames={i:"i32",j:"i64",f:"f32",d:"f64",e:"externref",p:"i32"};var type={parameters:[],results:sig[0]=="v"?[]:[typeNames[sig[0]]]};for(var i=1;i{var sigRet=sig.slice(0,1);var sigParam=sig.slice(1);var typeCodes={i:127,p:127,j:126,f:125,d:124,e:111};target.push(96);uleb128Encode(sigParam.length,target);for(var i=0;i{if(typeof WebAssembly.Function=="function"){return new WebAssembly.Function(sigToWasmTypes(sig),func)}var typeSectionBody=[1];generateFuncType(sig,typeSectionBody);var bytes=[0,97,115,109,1,0,0,0,1];uleb128Encode(typeSectionBody.length,bytes);bytes.push(...typeSectionBody);bytes.push(2,7,1,1,101,1,102,0,0,7,5,1,1,102,0,0);var module=new WebAssembly.Module(new Uint8Array(bytes));var instance=new WebAssembly.Instance(module,{e:{f:func}});var wrappedFunc=instance.exports["f"];return wrappedFunc};var updateTableMap=(offset,count)=>{if(functionsInTableMap){for(var i=offset;i{if(!functionsInTableMap){functionsInTableMap=new WeakMap;updateTableMap(0,wasmTable.length)}return functionsInTableMap.get(func)||0};var freeTableIndexes=[];var getEmptyTableSlot=()=>{if(freeTableIndexes.length){return freeTableIndexes.pop()}try{wasmTable.grow(1)}catch(err){if(!(err instanceof RangeError)){throw err}throw"Unable to grow wasm table. Set ALLOW_TABLE_GROWTH."}return wasmTable.length-1};var setWasmTableEntry=(idx,func)=>{wasmTable.set(idx,func);wasmTableMirror[idx]=wasmTable.get(idx)};var addFunction=(func,sig)=>{var rtn=getFunctionAddress(func);if(rtn){return rtn}var ret=getEmptyTableSlot();try{setWasmTableEntry(ret,func)}catch(err){if(!(err instanceof TypeError)){throw err}var wrapped=convertJsFunctionToWasm(func,sig);setWasmTableEntry(ret,wrapped)}functionsInTableMap.set(func,ret);return ret};var removeFunction=index=>{functionsInTableMap.delete(getWasmTableEntry(index));setWasmTableEntry(index,null);freeTableIndexes.push(index)};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();var wasmImports={m:___syscall_fcntl64,J:___syscall_ioctl,K:___syscall_openat,O:__abort_js,L:__emscripten_get_now_is_monotonic,N:__emscripten_memcpy_js,E:__emscripten_throw_longjmp,n:__munmap_js,M:_emscripten_date_now,j:_emscripten_get_now,F:_emscripten_resize_heap,G:_environ_get,H:_environ_sizes_get,Q:_exit,k:_fd_close,I:_fd_read,o:_fd_seek,l:_fd_write,i:invoke_ii,d:invoke_iii,h:invoke_iiii,A:invoke_iij,C:invoke_iijjii,x:invoke_jii,f:invoke_vi,e:invoke_vii,c:invoke_viii,b:invoke_viiii,a:invoke_viiiii,g:invoke_viiiiii,P:invoke_viiiiiii,p:invoke_viiiiiji,q:invoke_viiiiji,r:invoke_viiij,s:invoke_viiiji,w:invoke_viij,D:invoke_viiji,z:invoke_viijj,v:invoke_viijji,y:invoke_viijjj,B:invoke_vij,t:invoke_viji,u:invoke_vijj};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["S"])();var _uc2_open=Module["_uc2_open"]=(a0,a1)=>(_uc2_open=Module["_uc2_open"]=wasmExports["T"])(a0,a1);var _uc2_close=Module["_uc2_close"]=a0=>(_uc2_close=Module["_uc2_close"]=wasmExports["U"])(a0);var _uc2_strerror=Module["_uc2_strerror"]=a0=>(_uc2_strerror=Module["_uc2_strerror"]=wasmExports["V"])(a0);var _uc2_version=Module["_uc2_version"]=(a0,a1)=>(_uc2_version=Module["_uc2_version"]=wasmExports["W"])(a0,a1);var _uc2_mem_map=Module["_uc2_mem_map"]=(a0,a1,a2)=>(_uc2_mem_map=Module["_uc2_mem_map"]=wasmExports["X"])(a0,a1,a2);var _uc2_mem_unmap=Module["_uc2_mem_unmap"]=(a0,a1,a2)=>(_uc2_mem_unmap=Module["_uc2_mem_unmap"]=wasmExports["Y"])(a0,a1,a2);var _uc2_mem_protect=Module["_uc2_mem_protect"]=(a0,a1,a2,a3)=>(_uc2_mem_protect=Module["_uc2_mem_protect"]=wasmExports["Z"])(a0,a1,a2,a3);var _uc2_mem_write=Module["_uc2_mem_write"]=(a0,a1,a2,a3)=>(_uc2_mem_write=Module["_uc2_mem_write"]=wasmExports["_"])(a0,a1,a2,a3);var _malloc=Module["_malloc"]=a0=>(_malloc=Module["_malloc"]=wasmExports["$"])(a0);var _free=Module["_free"]=a0=>(_free=Module["_free"]=wasmExports["aa"])(a0);var _uc2_mem_read=Module["_uc2_mem_read"]=(a0,a1,a2,a3)=>(_uc2_mem_read=Module["_uc2_mem_read"]=wasmExports["ba"])(a0,a1,a2,a3);var _uc2_reg_write=Module["_uc2_reg_write"]=(a0,a1,a2)=>(_uc2_reg_write=Module["_uc2_reg_write"]=wasmExports["ca"])(a0,a1,a2);var _uc2_reg_read=Module["_uc2_reg_read"]=(a0,a1)=>(_uc2_reg_read=Module["_uc2_reg_read"]=wasmExports["da"])(a0,a1);var _uc2_emu_start=Module["_uc2_emu_start"]=(a0,a1,a2,a3,a4)=>(_uc2_emu_start=Module["_uc2_emu_start"]=wasmExports["ea"])(a0,a1,a2,a3,a4);var _uc2_emu_stop=Module["_uc2_emu_stop"]=a0=>(_uc2_emu_stop=Module["_uc2_emu_stop"]=wasmExports["fa"])(a0);var _uc2_hook_add_code=Module["_uc2_hook_add_code"]=(a0,a1,a2)=>(_uc2_hook_add_code=Module["_uc2_hook_add_code"]=wasmExports["ga"])(a0,a1,a2);var _uc2_hook_del=Module["_uc2_hook_del"]=(a0,a1)=>(_uc2_hook_del=Module["_uc2_hook_del"]=wasmExports["ha"])(a0,a1);var _uc2_set_code_hook_cb=Module["_uc2_set_code_hook_cb"]=a0=>(_uc2_set_code_hook_cb=Module["_uc2_set_code_hook_cb"]=wasmExports["ia"])(a0);var _uc2_hook_add_mem_invalid=Module["_uc2_hook_add_mem_invalid"]=a0=>(_uc2_hook_add_mem_invalid=Module["_uc2_hook_add_mem_invalid"]=wasmExports["ja"])(a0);var _uc2_set_mem_hook_cb=Module["_uc2_set_mem_hook_cb"]=a0=>(_uc2_set_mem_hook_cb=Module["_uc2_set_mem_hook_cb"]=wasmExports["ka"])(a0);var _uc2_scratch_alloc=Module["_uc2_scratch_alloc"]=a0=>(_uc2_scratch_alloc=Module["_uc2_scratch_alloc"]=wasmExports["la"])(a0);var _uc2_scratch_free=Module["_uc2_scratch_free"]=a0=>(_uc2_scratch_free=Module["_uc2_scratch_free"]=wasmExports["ma"])(a0);var _emscripten_builtin_memalign=(a0,a1)=>(_emscripten_builtin_memalign=wasmExports["oa"])(a0,a1);var _setThrew=(a0,a1)=>(_setThrew=wasmExports["pa"])(a0,a1);var __emscripten_stack_restore=a0=>(__emscripten_stack_restore=wasmExports["qa"])(a0);var _emscripten_stack_get_current=()=>(_emscripten_stack_get_current=wasmExports["ra"])();var dynCall_viiji=Module["dynCall_viiji"]=(a0,a1,a2,a3,a4,a5)=>(dynCall_viiji=Module["dynCall_viiji"]=wasmExports["sa"])(a0,a1,a2,a3,a4,a5);var dynCall_iijjii=Module["dynCall_iijjii"]=(a0,a1,a2,a3,a4,a5,a6,a7)=>(dynCall_iijjii=Module["dynCall_iijjii"]=wasmExports["ta"])(a0,a1,a2,a3,a4,a5,a6,a7);var dynCall_viiij=Module["dynCall_viiij"]=(a0,a1,a2,a3,a4,a5)=>(dynCall_viiij=Module["dynCall_viiij"]=wasmExports["ua"])(a0,a1,a2,a3,a4,a5);var dynCall_viji=Module["dynCall_viji"]=(a0,a1,a2,a3,a4)=>(dynCall_viji=Module["dynCall_viji"]=wasmExports["va"])(a0,a1,a2,a3,a4);var dynCall_vij=Module["dynCall_vij"]=(a0,a1,a2,a3)=>(dynCall_vij=Module["dynCall_vij"]=wasmExports["wa"])(a0,a1,a2,a3);var dynCall_viijj=Module["dynCall_viijj"]=(a0,a1,a2,a3,a4,a5,a6)=>(dynCall_viijj=Module["dynCall_viijj"]=wasmExports["xa"])(a0,a1,a2,a3,a4,a5,a6);var dynCall_jii=Module["dynCall_jii"]=(a0,a1,a2)=>(dynCall_jii=Module["dynCall_jii"]=wasmExports["ya"])(a0,a1,a2);var dynCall_viij=Module["dynCall_viij"]=(a0,a1,a2,a3,a4)=>(dynCall_viij=Module["dynCall_viij"]=wasmExports["za"])(a0,a1,a2,a3,a4);var dynCall_vijj=Module["dynCall_vijj"]=(a0,a1,a2,a3,a4,a5)=>(dynCall_vijj=Module["dynCall_vijj"]=wasmExports["Aa"])(a0,a1,a2,a3,a4,a5);var dynCall_viijji=Module["dynCall_viijji"]=(a0,a1,a2,a3,a4,a5,a6,a7)=>(dynCall_viijji=Module["dynCall_viijji"]=wasmExports["Ba"])(a0,a1,a2,a3,a4,a5,a6,a7);var dynCall_iij=Module["dynCall_iij"]=(a0,a1,a2,a3)=>(dynCall_iij=Module["dynCall_iij"]=wasmExports["Ca"])(a0,a1,a2,a3);var dynCall_viijjj=Module["dynCall_viijjj"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8)=>(dynCall_viijjj=Module["dynCall_viijjj"]=wasmExports["Da"])(a0,a1,a2,a3,a4,a5,a6,a7,a8);var dynCall_viiiji=Module["dynCall_viiiji"]=(a0,a1,a2,a3,a4,a5,a6)=>(dynCall_viiiji=Module["dynCall_viiiji"]=wasmExports["Ea"])(a0,a1,a2,a3,a4,a5,a6);var dynCall_viiiiji=Module["dynCall_viiiiji"]=(a0,a1,a2,a3,a4,a5,a6,a7)=>(dynCall_viiiiji=Module["dynCall_viiiiji"]=wasmExports["Fa"])(a0,a1,a2,a3,a4,a5,a6,a7);var dynCall_viiiiiji=Module["dynCall_viiiiiji"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8)=>(dynCall_viiiiiji=Module["dynCall_viiiiiji"]=wasmExports["Ga"])(a0,a1,a2,a3,a4,a5,a6,a7,a8);function invoke_viii(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiji(index,a1,a2,a3,a4,a5){var sp=stackSave();try{dynCall_viiji(index,a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iijjii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return dynCall_iijjii(index,a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vij(index,a1,a2,a3){var sp=stackSave();try{dynCall_vij(index,a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iij(index,a1,a2,a3){var sp=stackSave();try{return dynCall_iij(index,a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viijj(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{dynCall_viijj(index,a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viijjj(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{dynCall_viijjj(index,a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jii(index,a1,a2){var sp=stackSave();try{return dynCall_jii(index,a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viij(index,a1,a2,a3,a4){var sp=stackSave();try{dynCall_viij(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viijji(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{dynCall_viijji(index,a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijj(index,a1,a2,a3,a4,a5){var sp=stackSave();try{dynCall_vijj(index,a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viji(index,a1,a2,a3,a4){var sp=stackSave();try{dynCall_viji(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiji(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{dynCall_viiiji(index,a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiij(index,a1,a2,a3,a4,a5){var sp=stackSave();try{dynCall_viiij(index,a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiji(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{dynCall_viiiiji(index,a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiji(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{dynCall_viiiiiji(index,a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function applySignatureConversions(wasmExports){wasmExports=Object.assign({},wasmExports);var makeWrapper_pp=f=>a0=>f(a0)>>>0;var makeWrapper_ppp=f=>(a0,a1)=>f(a0,a1)>>>0;var makeWrapper_p=f=>()=>f()>>>0;wasmExports["$"]=makeWrapper_pp(wasmExports["$"]);wasmExports["oa"]=makeWrapper_ppp(wasmExports["oa"]);wasmExports["_emscripten_stack_alloc"]=makeWrapper_pp(wasmExports["_emscripten_stack_alloc"]);wasmExports["ra"]=makeWrapper_p(wasmExports["ra"]);return wasmExports}Module["addFunction"]=addFunction;Module["removeFunction"]=removeFunction;Module["setValue"]=setValue;Module["getValue"]=getValue;Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();moduleRtn=readyPromise; + + + return moduleRtn; +} +); +})(); +export default UnicornModule; diff --git a/frontend/src/apple/sap/vendor/unicorn.wasm b/frontend/src/apple/sap/vendor/unicorn.wasm new file mode 100755 index 0000000000000000000000000000000000000000..19b6a910676313d88652b05d2ac2dde9e38cd4d5 GIT binary patch literal 730937 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

`JQ!~bJ|+-^d7woc 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

-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

>!qA2XSY1 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 Wjm_)>MFF=dFNmkRiw2Wf_x}Ll>bB(o literal 0 HcmV?d00001 diff --git a/frontend/src/apple/sap/worker.ts b/frontend/src/apple/sap/worker.ts new file mode 100644 index 00000000..1da960a5 --- /dev/null +++ b/frontend/src/apple/sap/worker.ts @@ -0,0 +1,143 @@ +// SAP signer worker: hosts the TCI emulation so its synchronous bursts never +// block the UI thread. Speaks a tiny request/response protocol with the main +// thread (see client.ts); all Apple network traffic stays on the main thread. + +import { SapMachine } from "./machine"; +import type { SapAssetBundle } from "./types"; + +interface OpenRequest { + type: "open"; + id: number; + assets: { [K in keyof SapAssetBundle]: ArrayBuffer }; + wasmBinary: ArrayBuffer; +} + +interface InitializeRequest { + type: "initialize"; + id: number; + hardwareID: ArrayBuffer; +} + +interface ExchangeRequest { + type: "exchange"; + id: number; + version: number; + hardwareID: ArrayBuffer; + contextValue: number; + input: ArrayBuffer; +} + +interface SignRequest { + type: "sign"; + id: number; + contextValue: number; + input: ArrayBuffer; +} + +interface TeardownRequest { + type: "teardown"; + id: number; + contextValue: number; +} + +interface CloseRequest { + type: "close"; + id: number; +} + +type WorkerRequest = + | OpenRequest + | InitializeRequest + | ExchangeRequest + | SignRequest + | TeardownRequest + | CloseRequest; + +let machine: SapMachine | null = null; +let hardwareID: Uint8Array | null = null; + +function reply(id: number, payload: Record): void { + self.postMessage({ type: "result", id, ...payload }); +} + +function replyError(id: number, message: string): void { + self.postMessage({ type: "error", id, message }); +} + +self.onmessage = async (event: MessageEvent) => { + const request = event.data; + try { + switch (request.type) { + case "open": { + if (machine) { + machine.close(); + } + machine = await SapMachine.open( + { + commerceKit: new Uint8Array(request.assets.commerceKit), + commerceCore: new Uint8Array(request.assets.commerceCore), + coreFP: new Uint8Array(request.assets.coreFP), + coreFPICXS: new Uint8Array(request.assets.coreFPICXS), + }, + { wasmBinary: request.wasmBinary }, + ); + hardwareID = new Uint8Array(0); + reply(request.id, {}); + break; + } + case "initialize": { + if (!machine) { + throw new Error("SAP machine is not open"); + } + hardwareID = new Uint8Array(request.hardwareID); + const contextValue = machine.initialize(hardwareID); + reply(request.id, { contextValue }); + break; + } + case "exchange": { + if (!machine || !hardwareID) { + throw new Error("SAP machine is not open"); + } + const result = machine.exchange( + request.version, + new Uint8Array(request.hardwareID), + request.contextValue, + new Uint8Array(request.input), + ); + reply(request.id, { + output: result.output.buffer, + state: result.state, + }); + break; + } + case "sign": { + if (!machine) { + throw new Error("SAP machine is not open"); + } + const signature = machine.sign( + request.contextValue, + new Uint8Array(request.input), + ); + reply(request.id, { signature: signature.buffer }); + break; + } + case "teardown": { + machine?.teardown(request.contextValue); + reply(request.id, {}); + break; + } + case "close": { + machine?.close(); + machine = null; + hardwareID = null; + reply(request.id, {}); + break; + } + } + } catch (error) { + replyError( + request.id, + error instanceof Error ? error.message : String(error), + ); + } +}; diff --git a/frontend/tests/sap/machImage.test.ts b/frontend/tests/sap/machImage.test.ts new file mode 100644 index 00000000..788bdfd4 --- /dev/null +++ b/frontend/tests/sap/machImage.test.ts @@ -0,0 +1,233 @@ +import { describe, expect, it } from "vitest"; +import { MachImage } from "../../src/apple/sap/machImage"; + +// Minimal little-endian Mach-O 64 builder for opcode-level tests. +function buildMachO(options: { + segments: Array<{ + name: string; + vmaddr: number; + vmsize: number; + fileoff: number; + filesize: number; + data: Uint8Array; + }>; + rebaseOpcodes?: Uint8Array; + bindOpcodes?: Uint8Array; + lazyBindOpcodes?: Uint8Array; + symbols?: Array<{ name: string; value: number }>; +}): Uint8Array { + const { + segments, + rebaseOpcodes = new Uint8Array([0x00]), + bindOpcodes = new Uint8Array([0x00]), + lazyBindOpcodes = new Uint8Array([0x00]), + symbols = [], + } = options; + + const sizeofcmds = + segments.length * 72 + 24 + 48; // LC_SEGMENT_64s + LC_SYMTAB + LC_DYLD_INFO_ONLY + // Layout: header+load commands, then each segment's file data back-to-back + // from 0x1000, then symbol/opcode tables in a trailing scratch region. + const dataStart = Math.max(0x1000, 32 + sizeofcmds + 0x40); + let dataCursor = dataStart; + const placements: number[] = []; + for (const segment of segments) { + placements.push(dataCursor); + dataCursor += segment.data.length; + } + const totalSize = dataCursor + 0x1000; + const image = new Uint8Array(totalSize); + const view = new DataView(image.buffer); + + view.setUint32(0, 0xfeedfacf, true); // MH_MAGIC_64 + view.setUint32(4, 0x01000007, true); // CPU_TYPE_X86_64 + view.setUint32(16, segments.length + 2, true); // ncmds + + let offset = 32; + for (let index = 0; index < segments.length; index++) { + const segment = segments[index]; + const fileoff = placements[index]; + view.setUint32(offset, 0x19, true); // LC_SEGMENT_64 + view.setUint32(offset + 4, 72, true); + const nameBytes = new TextEncoder().encode(segment.name); + image.set(nameBytes.subarray(0, 16), offset + 8); + image.fill(0x20, offset + 8 + nameBytes.length, offset + 24); + view.setBigUint64(offset + 24, BigInt(segment.vmaddr), true); + view.setBigUint64(offset + 32, BigInt(segment.vmsize), true); + view.setBigUint64(offset + 40, BigInt(fileoff), true); + view.setBigUint64(offset + 48, BigInt(segment.data.length), true); + image.set(segment.data, fileoff); + offset += 72; + } + + // LC_SYMTAB + const stringTable = new Uint8Array(0x100); + const stringView = new DataView(stringTable.buffer); + const nlists: number[] = []; + let stringCursor = 1; + for (const symbol of symbols) { + const encoded = new TextEncoder().encode(symbol.name); + stringTable.set(encoded, stringCursor); + nlists.push(stringCursor); + stringView.setUint8(stringCursor + encoded.length, 0); + stringCursor += encoded.length + 1; + } + const symoff = totalSize - 0x800; + const stroff = symoff - 0x100; + let cursor = symoff; + for (let index = 0; index < symbols.length; index++) { + view.setUint32(cursor, nlists[index], true); + view.setUint8(cursor + 4, 0x0f); // N_EXT | N_SECT + view.setUint8(cursor + 5, 1); + view.setUint16(cursor + 6, 0, true); + view.setBigUint64(cursor + 8, BigInt(symbols[index].value), true); + cursor += 16; + } + image.set(stringTable, stroff); + + view.setUint32(offset, 0x2, true); // LC_SYMTAB + view.setUint32(offset + 4, 24, true); + view.setUint32(offset + 8, symoff, true); + view.setUint32(offset + 12, symbols.length, true); + view.setUint32(offset + 16, stroff, true); + view.setUint32(offset + 20, stringTable.length, true); + offset += 24; + + // LC_DYLD_INFO_ONLY + const rebaseOff = totalSize - 0x400; + const bindOff = totalSize - 0x300; + const lazyOff = totalSize - 0x200; + image.set(rebaseOpcodes, rebaseOff); + image.set(bindOpcodes, bindOff); + image.set(lazyBindOpcodes, lazyOff); + + view.setUint32(offset, 0x80000022, true); + view.setUint32(offset + 4, 48, true); + view.setUint32(offset + 8, rebaseOff, true); + view.setUint32(offset + 12, rebaseOpcodes.length, true); + view.setUint32(offset + 16, bindOff, true); + view.setUint32(offset + 20, bindOpcodes.length, true); + view.setUint32(offset + 24, 0, true); // weak off + view.setUint32(offset + 28, 0, true); + view.setUint32(offset + 32, lazyOff, true); + view.setUint32(offset + 36, lazyBindOpcodes.length, true); + return image; +} + +const segment = (name: string, size: number) => ({ + name, + vmaddr: 0, + vmsize: size, + fileoff: 0x1000, + filesize: size, + data: new Uint8Array(size), +}); + +describe("MachImage", () => { + it("resolves exported symbols and applies the load-base slide", () => { + const image = MachImage.open( + "test", + buildMachO({ + segments: [segment("__TEXT", 0x1000)], + symbols: [{ name: "_entry", value: 0x1234 }], + }), + ); + expect(image.export("_entry", 0x5000)).toBe(0x5000 + 0x1234); + expect(() => image.export("_missing", 0)).toThrow(/symbol not found/); + }); + + it("parses classic rebase opcodes and reads original pointers from the image", () => { + const data = new Uint8Array(0x1000); + const view = new DataView(data.buffer); + view.setBigUint64(0x18, 0x1122334455667788n, true); // original pointer at __DATA+0x18 + const segments = [ + { ...segment("__DATA", 0x1000), data }, + segment("__LINKEDIT", 0x100), + ]; + + // SET_TYPE(POINTER=1); SET_SEGMENT(1) offset 0x10; DO_REBASE_IMM x3 + const rebase = new Uint8Array([0x11, 0x21, 0x10, 0x53, 0x00]); + const image = MachImage.open( + "test", + buildMachO({ segments, rebaseOpcodes: rebase }), + ); + // The private parses are exercised through relocate/load; verify the + // public contract instead: relocate succeeds and the image loads. + const fakeMemory = { + mapped: [] as Array<[number, number]>, + memMap(address: number, size: number) { + this.mapped.push([address, size]); + }, + memWrite() {}, + }; + image.relocate(0x100000000, () => 0x2000); + image.load(fakeMemory); + expect(fakeMemory.mapped.length).toBeGreaterThan(0); + }); + + it("bind opcodes resolve imports with addends and segment-relative offsets", () => { + const data = new Uint8Array(0x1000); + const segments = [ + { ...segment("__DATA", 0x1000), data }, + segment("__LINKEDIT", 0x100), + ]; + // ordinal(2); symbol _malloc\0; type POINTER; segment 0 offset 0x20; + // addend +8; DO_BIND; DONE + const symbol = new TextEncoder().encode("_malloc"); + const bind = new Uint8Array([ + 0x12, + 0x40, + ...symbol, + 0x00, + 0x51, + 0x70, + 0x20, + 0x60, + 0x08, + 0x90, + 0x00, + ]); + const image = MachImage.open( + "test", + buildMachO({ segments, bindOpcodes: bind }), + ); + image.relocate(0x100000000, (name) => { + if (name === "_malloc") { + return 0xdeadbeef; + } + return 0; + }); + // Relocation patches the image copy; the first segment lands at 0x1000. + const relocated = image.debugBytes(); + const view = new DataView( + relocated.buffer, + relocated.byteOffset + 0x1020, + ); + expect(Number(view.getBigUint64(0, true))).toBe(0xdeadbeef + 8); + }); + + it("skips BSS-tail fixups instead of failing", () => { + // __DATA vmsize 0x2000, filesize 0x1000: fixup at 0x1008 targets BSS. + const data = new Uint8Array(0x1000); + const segments = [ + { + name: "__DATA", + vmaddr: 0, + vmsize: 0x2000, + fileoff: 0x1000, + filesize: 0x1000, + data, + }, + segment("__LINKEDIT", 0x100), + ]; + const symbol = new TextEncoder().encode("_x"); + const bind = new Uint8Array([ + 0x12, 0x40, ...symbol, 0x00, 0x51, 0x70, 0x08, 0x10, 0x90, 0x00, + ]); + const image = MachImage.open( + "test", + buildMachO({ segments, bindOpcodes: bind }), + ); + expect(() => image.relocate(0x100000000, () => 0x1234)).not.toThrow(); + }); +}); diff --git a/frontend/tests/sap/machine-live.mjs b/frontend/tests/sap/machine-live.mjs new file mode 100644 index 00000000..2667a0b8 --- /dev/null +++ b/frontend/tests/sap/machine-live.mjs @@ -0,0 +1,62 @@ +// Full-chain SAP machine test with the real Apple assets (no network): +// open images -> Initialize -> Sign. Run with: npx tsx tests/sap/machine-live.mjs +import { readFileSync } from "node:fs"; +import { SapMachine } from "../../src/apple/sap/machine.ts"; + +// Asset layout: either SAP_ASSET_DIR pointing at a flat directory holding +// the four files, or the nested structure produced by the shell extraction. +import { existsSync } from "node:fs"; +const FLAT = process.env.SAP_ASSET_DIR ?? "/tmp/sap-backend-test/sap-assets"; +const NESTED = "/tmp/sap-extract/extracted/System/Library/PrivateFrameworks"; +const DIR = existsSync(`${FLAT}/CoreFP`) + ? null // flat layout handled below + : NESTED; +const CK = DIR ? `${DIR}/CommerceKit.framework/Versions/A/CommerceKit` : `${FLAT}/CommerceKit`; +const CC = DIR + ? `${DIR}/CommerceKit.framework/Versions/A/Frameworks/CommerceCore.framework/Versions/A/CommerceCore` + : `${FLAT}/CommerceCore`; +const CFP = DIR ? `${DIR}/CoreFP.framework/Versions/A/CoreFP` : `${FLAT}/CoreFP`; +const ICXS = DIR ? `${DIR}/CoreFP.framework/Versions/A/CoreFP.icxs` : `${FLAT}/CoreFP.icxs`; +const assets = { + coreFP: new Uint8Array(readFileSync(CFP)), + coreFPICXS: new Uint8Array(readFileSync(ICXS)), + commerceKit: new Uint8Array(readFileSync(CK)), + commerceCore: new Uint8Array(readFileSync(CC)), +}; +console.log( + "assets loaded:", + Object.entries(assets).map(([k, v]) => `${k}=${v.length}`).join(" "), +); + +const wasmBinary = readFileSync(new URL("../../src/apple/sap/vendor/unicorn.wasm", import.meta.url)); + +const t0 = Date.now(); +const machine = await SapMachine.open(assets, { wasmBinary }); +console.log(`machine open OK (${Date.now() - t0}ms)`); + +const hardwareID = new TextEncoder().encode("0123456789ab"); +const t1 = Date.now(); +const context = machine.initialize(hardwareID); +console.log(`initialize OK (${Date.now() - t1}ms): context = 0x${context.toString(16)}`); + +const t2 = Date.now(); +const body = new TextEncoder().encode( + 'appleIdtest@example.com', +); +// Signing requires the Apple key exchange (GET certificate + POST setup). +// Without it the guest reports -42085 on both the native ipatool runtime and +// this port; that exact match is the assertion. +let signStatus = "none"; +try { + machine.sign(context, body); +} catch (e) { + signStatus = e.message.match(/-?\d+/)?.[0] ?? e.message; +} +if (signStatus !== "-42085") { + throw new Error(`expected sign status -42085, got: ${signStatus}`); +} +console.log(`sign gated on key exchange (${Date.now() - t2}ms): -42085 as native`); + +machine.teardown(context); +machine.close(); +console.log("FULL CHAIN OK"); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 687769ba..1a02ab0b 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -4,6 +4,11 @@ import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [react(), tailwindcss()], + // The SAP signer worker dynamically imports the emscripten module, which + // requires ES-module workers (code splitting is unsupported under iife). + worker: { + format: "es", + }, server: { proxy: { "/api": "http://localhost:8080", From 3e0113de7c9cd0238614055639ca8ac5a2bf45fe Mon Sep 17 00:00:00 2001 From: Hubert Date: Fri, 4 Sep 2026 15:07:59 +0800 Subject: [PATCH 2/4] Adopt upstream #88 UX: singleton signer, warmup, progress indicator The upstream draft PR (Lakr233/AssppWeb#88) solved the same problem with an npm unicorn build; three of its UX decisions are worth taking regardless of engine: - Signer singleton bound to the deviceIdentifier. Rebuilding per authenticate() call copied the 22.5 MB asset bundle into a fresh worker on every attempt (2FA retries included); the singleton reuses the worker and SAP session across attempts and rebuilds only when the account or bag endpoints change. - Background warmup (useSapWarmup): once an account exists, fetch the bag and prepare the signer so sign-in usually finds it ready. The one-time asset download dominates preparation; later runs are served from the Cache API. - Inline progress (SapStatus) wired to a zustand store, with strings in all five locales. Also documents the upstream finding that the live bag hands out the legacy MZFinance authenticate endpoint and all advertised endpoints are covered by sign-sap-request. --- frontend/src/apple/authenticate.ts | 20 +-- frontend/src/apple/sap/client.ts | 165 ++++++++++++++---- .../src/components/Account/AccountList.tsx | 2 + .../src/components/Account/AddAccountForm.tsx | 2 + frontend/src/components/common/SapStatus.tsx | 34 ++++ frontend/src/hooks/useSapWarmup.ts | 42 +++++ frontend/src/locales/en-US.json | 5 +- frontend/src/locales/ja.json | 5 +- frontend/src/locales/ko.json | 5 +- frontend/src/locales/zh-CN.json | 5 +- frontend/src/locales/zh-TW.json | 5 +- frontend/src/store/sap.ts | 37 ++++ 12 files changed, 272 insertions(+), 55 deletions(-) create mode 100644 frontend/src/components/common/SapStatus.tsx create mode 100644 frontend/src/hooks/useSapWarmup.ts create mode 100644 frontend/src/store/sap.ts diff --git a/frontend/src/apple/authenticate.ts b/frontend/src/apple/authenticate.ts index e7c77d7b..f0ad8893 100644 --- a/frontend/src/apple/authenticate.ts +++ b/frontend/src/apple/authenticate.ts @@ -3,9 +3,7 @@ import { appleRequest } from "./request"; import { buildPlist, parsePlist } from "./plist"; import { extractAndMergeCookies } from "./cookies"; import { fetchBag, defaultAuthURL } from "./bag"; -import { createSapSigner } from "./sap/client"; -import { loadSapAssets } from "./sap/assets"; -import type { SapSigner } from "./sap/signer"; +import { prepareSigner } from "./sap/client"; import i18n from "../i18n"; export class AuthenticationError extends Error { @@ -43,15 +41,11 @@ export async function authenticate( // When the bag advertises the SAP signing protocol, every request to the // auth endpoint must carry X-Apple-ActionSignature over its body bytes. // The signer sees only the hardware ID and public Apple assets — never the - // password — because signing happens here in the browser. - let sapSigner: SapSigner | null = null; + // password — because signing happens here in the browser. It is kept as a + // singleton between attempts (2FA retries reuse the same session). + let sapSigner = null as Awaited> | null; if (bag.sapEndpoints) { - const assets = await loadSapAssets(); - sapSigner = await createSapSigner({ - ...bag.sapEndpoints, - hardwareID: new TextEncoder().encode(deviceId), - assets, - }); + sapSigner = await prepareSigner(deviceId, bag.sapEndpoints); } let currentAttempt = 0; @@ -174,18 +168,14 @@ export async function authenticate( pod, }; - await sapSigner?.close().catch(() => undefined); - sapSigner = null; return account; } catch (e) { if (e instanceof AuthenticationError) { - await sapSigner?.close().catch(() => undefined); throw e; } lastError = e instanceof Error ? e : new Error(String(e)); } } - await sapSigner?.close().catch(() => undefined); throw lastError ?? new Error(i18n.t("errors.auth.unknownReason")); } diff --git a/frontend/src/apple/sap/client.ts b/frontend/src/apple/sap/client.ts index 1ce62161..f548865d 100644 --- a/frontend/src/apple/sap/client.ts +++ b/frontend/src/apple/sap/client.ts @@ -1,19 +1,17 @@ -// Main-thread SAP signer client: spawns the emulation worker, shuttles -// machine calls to it, and exposes the SapSigner orchestration used by the -// authentication flow. +// Main-thread SAP signer manager. +// +// The signer is kept as a module-level singleton bound to the hardware id it +// was initialized with: creating the worker copies the ~22.5 MB asset bundle +// into it, and repeating that per sign-in (2FA retries included) is pure +// waste. `prepareSigner` reuses a matching signer or rebuilds for a different +// account; concurrent callers share the same preparation. The zustand store +// in store/sap.ts carries progress for the UI. import { SapSigner, type SapMachineDriver } from "./signer"; import { exchangeSetupBuffer, fetchSetupCertificate } from "./protocol"; -import type { SapSignerOptions } from "./types"; - -async function loadWorkerWasmBinary(): Promise { - const url = new URL("./vendor/unicorn.wasm", import.meta.url); - const response = await fetch(url); - if (!response.ok) { - throw new Error(`SAP engine download failed: HTTP ${response.status}`); - } - return response.arrayBuffer(); -} +import { loadSapAssets } from "./assets"; +import type { SapEndpoints } from "./types"; +import { useSapStore } from "../../store/sap"; interface WorkerResult { type: "result"; @@ -27,6 +25,8 @@ interface WorkerError { message: string; } +const SETUP_TIMEOUT_MS = 2 * 60 * 1000; + class WorkerMachineDriver implements SapMachineDriver { private nextId = 1; private readonly pending = new Map< @@ -50,7 +50,7 @@ class WorkerMachineDriver implements SapMachineDriver { }; } - private call(request: Record): Promise { + call(request: Record): Promise { const id = this.nextId++; return new Promise((resolve, reject) => { this.pending.set(id, { resolve, reject }); @@ -58,7 +58,6 @@ class WorkerMachineDriver implements SapMachineDriver { }); } - /** Loads the emulation runtime into the worker. */ async open( assets: { commerceKit: Uint8Array; @@ -130,34 +129,130 @@ class WorkerMachineDriver implements SapMachineDriver { } } +interface PreparedSigner { + signer: SapSigner; + driver: WorkerMachineDriver; + hardwareID: string; + endpoints: SapEndpoints; +} + +let prepared: PreparedSigner | null = null; +let preparation: Promise | null = null; +let wasmBinary: ArrayBuffer | null = null; + +async function loadWorkerWasmBinary(): Promise { + if (wasmBinary) { + return wasmBinary; + } + const url = new URL("./vendor/unicorn.wasm", import.meta.url); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`SAP engine download failed: HTTP ${response.status}`); + } + wasmBinary = await response.arrayBuffer(); + return wasmBinary; +} + /** - * Creates a production signer: emulation runs in a dedicated worker, setup - * network calls flow through the wisp tunnel on the main thread. + * Returns a ready signer for the hardware id, reusing the current one when + * it matches. Concurrent callers share a single preparation; the setup + * network exchange rides the wisp tunnel on the main thread. */ -export async function createSapSigner( - options: SapSignerOptions, +export async function prepareSigner( + hardwareID: string, + endpoints: SapEndpoints, ): Promise { - const worker = new Worker( - new URL("./worker.ts", import.meta.url), - { type: "module" }, - ); - const driver = new WorkerMachineDriver(worker); + if ( + prepared && + prepared.hardwareID === hardwareID && + endpointsEqual(prepared.endpoints, endpoints) + ) { + return prepared.signer; + } + if (preparation) { + const underway = await preparation.catch(() => null); + if ( + underway && + underway.hardwareID === hardwareID && + endpointsEqual(underway.endpoints, endpoints) + ) { + return underway.signer; + } + } + preparation = runPreparation(hardwareID, endpoints); try { - await driver.open( - options.assets, - options.wasmBinary ?? (await loadWorkerWasmBinary()), + return (await preparation).signer; + } finally { + preparation = null; + } +} + +async function runPreparation( + hardwareID: string, + endpoints: SapEndpoints, +): Promise { + useSapStore.getState().begin(hardwareID); + + const previous = prepared; + prepared = null; + try { + // Tear down the old signer first: the worker holds ~160 MB of wasm heap. + await previous?.driver.close().catch(() => undefined); + + const assets = await loadSapAssets((loaded, total) => + useSapStore + .getState() + .setAssets(total ? Math.round((loaded / total) * 100) : 0), ); + useSapStore.getState().setSetup(); + + const worker = new Worker(new URL("./worker.ts", import.meta.url), { + type: "module", + }); + const driver = new WorkerMachineDriver(worker); + const wasm = await loadWorkerWasmBinary(); + await driver.open(assets, wasm); + + const signer = await SapSigner.create( + { + ...endpoints, + hardwareID: new TextEncoder().encode(hardwareID), + assets, + }, + driver, + { + fetchCertificate: () => fetchSetupCertificate(endpoints), + exchange: (input) => exchangeSetupBuffer(endpoints, input), + }, + ); + + const result = { signer, driver, hardwareID, endpoints }; + prepared = result; + useSapStore.getState().setReady(); + return result; } catch (error) { - worker.terminate(); + useSapStore + .getState() + .setError(error instanceof Error ? error.message : String(error)); throw error; } +} - return SapSigner.create(options, driver, { - fetchCertificate: () => fetchSetupCertificate(options), - exchange: (input) => exchangeSetupBuffer(options, input), - }).catch((error) => { - worker.terminate(); - throw error; - }); +/** Signs body bytes, preparing the signer on demand. */ +export async function signWithSap( + hardwareID: string, + endpoints: SapEndpoints, + body: Uint8Array, +): Promise { + const signer = await prepareSigner(hardwareID, endpoints); + return signer.sign(body); +} + +function endpointsEqual(left: SapEndpoints, right: SapEndpoints): boolean { + return ( + left.certificateURL === right.certificateURL && + left.setupURL === right.setupURL && + left.version === right.version + ); } diff --git a/frontend/src/components/Account/AccountList.tsx b/frontend/src/components/Account/AccountList.tsx index 022d688c..d663e37a 100644 --- a/frontend/src/components/Account/AccountList.tsx +++ b/frontend/src/components/Account/AccountList.tsx @@ -3,11 +3,13 @@ import { Link, NavLink } from "react-router-dom"; import { useTranslation } from "react-i18next"; import PageContainer from "../Layout/PageContainer"; import { useAccountsStore } from "../../store/accounts"; +import { useSapWarmup } from "../../hooks/useSapWarmup"; import { storeIdToCountry } from "../../apple/config"; export default function AccountList() { const { t } = useTranslation(); const { accounts, loading, loadAccounts } = useAccountsStore(); + useSapWarmup(); useEffect(() => { loadAccounts(); diff --git a/frontend/src/components/Account/AddAccountForm.tsx b/frontend/src/components/Account/AddAccountForm.tsx index 4c92bcda..ca8bc50e 100644 --- a/frontend/src/components/Account/AddAccountForm.tsx +++ b/frontend/src/components/Account/AddAccountForm.tsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import PageContainer from "../Layout/PageContainer"; import Spinner from "../common/Spinner"; +import SapStatus from "../common/SapStatus"; import { useAccounts } from "../../hooks/useAccounts"; import { useToastStore } from "../../store/toast"; import { authenticate, AuthenticationError } from "../../apple/authenticate"; @@ -59,6 +60,7 @@ export default function AddAccountForm() {

+