Infinite Mac is a full-stack TypeScript application whose central trick is compiling decades-old C and C++ emulator source code to WebAssembly so it can run inside a browser. Understanding how the pieces connect — from the React UI down to a chunked disk image sitting in an R2 bucket — makes it much easier to work on any part of the system.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/mihaip/infinite-mac/llms.txt
Use this file to discover all available pages before exploring further.
High-Level Architecture
infinitemac.org (and the shortcut domains) is handled by the Cloudflare Worker defined in worker/index.ts. The worker serves the React app shell via server-side rendering, delegates static asset delivery to the Cloudflare Assets binding, and exposes a WebSocket endpoint used by the EthernetZone Durable Object to bridge AppleTalk packets between sessions.
The RunDef Data Model
Every emulator session is fully described by a RunDef value defined in src/defs/run-def.ts:
RunDef is the single source of truth for what the emulator will boot. Two functions — runDefFromUrl and runDefToUrl — serialise and deserialise this structure to and from the page URL, making every session fully bookmarkable and shareable. Year-indexed paths like /1984/System%201.0 are normalised into a RunDef by runDefFromUrl; the custom /run and /embed routes use explicit query parameters for each field.
Emulator Integration Layers
The emulator code is split across three directories with clearly separated responsibilities:src/emulator/common/ — Shared types and utilities
Contains types and functions used by both the main thread and the worker. The most important export is EmulatorChunkedFileSpec, which describes a disk image as an array of content-addressed chunks:
common.ts also defines EmulatorWorkerConfig (the full serialisable configuration passed from the main thread to the worker), InputBufferAddresses (the fixed memory layout of the shared input buffer), and all EmulatorInputEvent variants (mouse, keyboard, speed, pause).
src/emulator/ui/ — Main thread driver
The UI layer runs on the browser’s main thread and owns everything the user sees and interacts with. Key modules:
ui.ts— theEmulatorclass; orchestrates startup, manages the worker lifecycle, and exposes the public API consumed by React componentsconfig.ts— translates aRunDefinto anEmulatorWorkerConfigby resolving disk specs, ROM paths, and prefs filesvideo.ts— reads the emulator’s framebuffer (viaSharedArrayBufferor fallbackpostMessage) and blits it to a<canvas>input.ts— translates browser mouse and keyboard events intoEmulatorInputEventvalues and writes them into the shared input bufferaudio.ts— routes emulator audio output through a Web AudioAudioWorkletNodefiles.ts— handles drag-and-drop file uploads and directory extractionsethernet.ts— connects anEmulatorEthernetProvider(Cloudflare Worker or BroadcastChannel) to the emulator’s network interface
src/emulator/worker/ — Worker runtime
The worker thread runs the WASM emulator binary and handles all emulator-side I/O:
worker.ts— entry point; receivesEmulatorWorkerConfig, instantiates the correct emulator, and drives the main loopchunked-disk.ts— implementsEmulatorWorkerDiskby fetching disk chunks synchronously over XHR (valid in workers) as the emulator requests sectorsdisks.ts— assembles the full set ofEmulatorWorkerDiskinstances from the config, including the Infinite HD image, Saved HD, and any uploaded disk filesemscripten/— pre-built WASM binaries and their Emscripten JS glue for all seven emulator cores
The WASM binaries in
src/emulator/worker/emscripten/ are pre-built and committed directly to the Git repository. You do not need Docker or Emscripten to work on the frontend or import scripts — rebuilding the emulator cores is only necessary when making changes to the C/C++ emulator source in the submodules. See the README for Docker-based build instructions for each core.Chunked Disk Images
Disk images for classic Mac systems can be hundreds of megabytes. Infinite Mac avoids loading them in full by splitting each image into fixed-size chunks and serving them lazily as the emulator requests sectors. The chunking pipeline (run bynpm run import-disks) works as follows:
- A raw disk image is split into
chunkSize-byte blocks (typically 256 KB per chunk for hard disk images, 128 KB for CD-ROMs). - Each non-zero chunk is content-addressed: its SHA-1 hash becomes its filename (
<hash>.chunk). - Zero-filled chunks (empty disk space) are omitted from the manifest entirely — a missing entry means “synthesise a zeroed block”.
- A JSON manifest (
EmulatorChunkedFileSpec) is written alongside the chunks and imported as a dynamic ES module by the React app at runtime.
EmulatorWorkerChunkedDisk in the worker fulfils every sector read by looking up the corresponding chunk index, fetching the URL synchronously if it hasn’t been loaded yet, and caching it in a Map<number, Uint8Array>. Writes go to the same in-memory cache (they are never persisted back to R2, except for the Saved HD which uses the Origin Private File System).
Prefetch hints — Each SystemDiskDef carries a prefetchChunks array listing the chunk indices needed to boot the system. The UI layer requests these in parallel before the emulator starts, cutting boot latency by eliminating the first round of synchronous XHR stalls.
SharedArrayBuffer and the Fallback Mode
The highest-performance communication path between the main thread and the emulator worker relies onSharedArrayBuffer. The input buffer (keyboard/mouse events), video framebuffer, audio ring buffer, file-transfer buffer, and Ethernet receive buffer are all SharedArrayBuffer instances allocated once and handed to both sides at startup. This eliminates serialisation overhead and lets the worker spin-wait on an atomic lock rather than yield to the event loop.
SharedArrayBuffer requires the page to be served with two HTTP headers:
<iframe> that does not opt in), the code automatically falls back to a postMessage-based mode where each subsystem has a corresponding EmulatorWorkerFallback*Config variant. The fallback is functionally complete but has higher latency.
The debugFallback flag in RunDef (exposed as the ?debug_fallback=true query parameter) forces the fallback path even on a correctly-isolated page, which is useful for testing that mode without a special embedding environment.
Directory Structure
Cloudflare Infrastructure
Thewrangler.jsonc at the repo root describes the complete production deployment:
- Assets binding — the
build/directory is served as a static site withsingle-page-applicationfallback, so all URLs resolve to the React app shell - R2 bucket (
infinite-mac-disk) — chunked disk image files are synced here bynpm run sync-disksand served by the Worker under the/disk/path prefix - KV namespace (
VARZ) — stores lightweight counters and telemetry variables - Durable Object (
EthernetZone) — one instance per named AppleTalk zone; bridges WebSocket connections from multiple browser sessions so they can exchange raw Ethernet frames - Routes —
system6.app,system7.app,macos8.app,macos9.app,kanjitalk7.app, andinfinitemac.org(plus their*.wildcard subdomains) all route to the same Worker