SearchA-ZA › Apple Newton MessagePad 2100

Apple Newton MessagePad 2100

1997 Online · runs in your browser GPL (Einstein) · grey ROM

The Apple Newton MessagePad 2100 (1997) was the last and most capable of Apple's Newton PDAs: a pen-driven handheld with cursive handwriting recognition, built around a StrongARM SA-110 and running Newton OS 2.1 from an 8 MB ROM. This build runs the whole machine in the browser through Einstein, Paul Guyot's open-source Newton emulator, compiled from source to WebAssembly. It boots the real MessagePad 2100 ROM to the Newton OS 2.1 first-run "Welcome" screen; the whole screen is the touchscreen, so you drive it with the stylus (mouse or touch). It plugs into the shared in-frame debugger, so you can single-step the StrongARM, read and write R0–R15 and the CPSR, inspect memory live, and set breakpoints and watchpoints.

Apple Newton MessagePad 2100 Online Emulator

Play Apple Newton MessagePad 2100 using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
MessagePad 2100 (Newton OS 2.1)Apple Newton MessagePad 2100Apple Newton MessagePad 2100greyOpen ⛶

Visit the official site ↗

Runs on: any modern browser — nothing to install.

Machines emulated

Chips

Notes

Embedding

The Newton core is Einstein (Paul Guyot's open-source Apple Newton emulator), compiled from source to WebAssembly with Emscripten. Einstein normally ships an FLTK / Cocoa / SDL desktop app with a pthread-based timer thread; rather than pull in any of that we wrote a small standalone frontend, newton_wasm.cpp, that drives Einstein's OWN core classes directly — TEmulator, TARMProcessor, TMemory, TInterruptManager, the Generic ARM interpreter and TNativePrimitives — with a TNullScreenManager (whose base class still rasterises the Newton screen into a 4-bit framebuffer) and a null sound / null network manager. The module is built with -sMODULARIZE=1, so newton.js is a factory you instantiate. We self-host everything (no CDN): newton.js / newton.wasm and, preloaded into the MEMFS, the 8 MB MessagePad 2100 ROM (717006) plus Einstein.rex.

// newton.js defines a global Module factory; instantiate it, then boot.
var nt = await Module({ locateFile: f => SRC + f });   // finds newton.wasm + newton.data
nt._newton_boot(4096);                                 // build the machine (4 MB RAM), ROM+REX from MEMFS
nt._newton_tick(600000);                             // run ~one frame of ARM instructions

Single-threaded cooperative timing. Einstein's TInterruptManager runs its 3.6864 MHz timer on a worker thread that signals the emulator thread through condition variables — impossible without SharedArrayBuffer. We added a gCooperative mode: Init() spawns no thread, and TEmulator::RunCoop(n) pumps the timer synchronously (CoopPump()) between short batches of the Generic interpreter's Step(), delivering FIQ/IRQ to the CPU. The Newton clock is driven by executed instructions (not host wall-clock), so idle waits (PauseSystem) fast-forward to the next timer match deterministically. No SharedArrayBuffer, no COOP/COEP headers, hosts anywhere.

Rendering. The Newton framebuffer is 320×480 at 4 bits/pixel (16 greys), 2 pixels per byte. Each frame we read it from the WASM heap through _newton_fb_ptr() and expand it to RGBA on an off-screen canvas:

var fb = nt.HEAPU8.subarray(nt._newton_fb_ptr(), ptr + 320*480/2);
for (var i=0; i<fb.length; i++){ var b=fb[i], hi=b>>4, lo=b&15; /* two pixels */ }

Input is the stylus. The whole canvas is the resistive touchscreen: pointer events map to screen pixels and call _newton_pen_down(x,y) / _newton_pen_up(), which drive Einstein's TScreenManager::PenDown / PenUp. The hardware power switch is _newton_power_button() (Einstein's SendPowerSwitchEvent).

MemberKindWhat it does
_newton_boot(ramKB)exportBuild TEmulator from the ROM+REX with a null screen/sound/network and cooperative timer.
_newton_tick(n)exportTEmulator::RunCoop(n) — run up to n ARM instructions, pumping the timer.
_newton_fb_ptr() / _fb_w() / _fb_h()exportPointer + size of the 320×480 4bpp framebuffer in the heap.
_newton_pen_down(x,y) / _newton_pen_up()exportSet / lift the stylus on the touchscreen.
_newton_power_button()exportToggle the hardware power switch.
_newton_dbg_*exportThe debug sampling API over the live StrongARM — see below.

Debugger integration

This is a from-source WebAssembly core, yet it gets the same live debugger as the pure-JavaScript machines — real registers, side-effect-free memory, a real single-instruction step, plus execution breakpoints and memory watchpoints. Einstein keeps the CPU in a plain C++ object, TARMProcessor, whose GetRegister() / SetRegister() / GetCPSR() reach the live StrongARM state and whose TEmulator::Step() runs exactly one ARM instruction through the Generic interpreter. We expose these through a small sampling API from our standalone frontend.

1 · Exactly what was added. One frontend file, newton_wasm.cpp, appends a block of EMSCRIPTEN_KEEPALIVE functions that read/write the live TARMProcessor and peek memory side-effect-free through TMemory::ReadBP / WriteBP (the same peek path Einstein's own monitor uses). They are sampling functions: the debugger calls them a few times a second while its window is open, and once per Step. There is no per-instruction or per-cycle callback anywhere — the hot path (RunCoop → the interpreter's Step) is unchanged (the golden performance rule).

// newton_wasm.cpp — sampling only, over Einstein's TARMProcessor / TMemory.
KUInt32 newton_dbg_reg(int i){ return gCPU->GetRegister(i & 15); }
KUInt32 newton_dbg_pc(void){ return gCPU->GetRegister(15); }
KUInt32 newton_dbg_cpsr(void){ return gCPU->GetCPSR(); }
int newton_dbg_read(KUInt32 a){ KUInt8 b=0; gMem->ReadBP(a, b); return b; }  // side-effect-free
void newton_dbg_step(int n){ while(n--){ im->CoopPump(); gEmulator->Step(); } } // ONE instruction

2 · What is REAL here.

  • Real registers — R0-R15 and the CPSR (N Z C V + I/F/T + mode bits) read and written live off the TARMProcessor. SP/LR/PC are the same array entries the interpreter executes from.
  • Real single-instruction stepStep i calls newton_dbg_stepTEmulator::Step() → the Generic interpreter's Step(&cpu, 1), which executes exactly one ARM instruction; PC and the registers change by one instruction per click.
  • Side-effect-free memory — reads go through TMemory::ReadBP, the peek path that bypasses hardware I/O side effects, so auto-polling the hex/disasm view never acknowledges an interrupt or clears a latch.
  • Execution breakpoints & memory watchpoints — implemented host-side in the run loop (below), so they cost nothing when the debugger is closed.

3 · The reused decoder. The Newton CPU is an ARM (StrongARM SA-110, ARMv4), so the machine plug-in points its cpu.decoder at the shared arm7 ARM disassembler — the very same decoder the WebAssembly ARM machines (Archimedes, Nintendo DS) use. No new decoder was written.

4 · Breakpoints and watchpoints without a hot-path hook. When nothing is armed, the loop runs a whole frame at native speed with _newton_tick(). As soon as a breakpoint or watchpoint is set, the loop switches to stepping one ARM instruction at a time with _newton_dbg_step(1), comparing the live PC against the breakpoint set and the watched addresses against their last sampled value, and pausing on a hit — a pure host-side check that only runs while a guard is armed.

Architecture

The Apple Newton MessagePad 2100 (Apple, 1997) is the last and most capable of the Newton PDAs — a pen-driven handheld with printing/cursive handwriting recognition, running Newton OS 2.1 from an 8 MB ROM. It is built around a StrongARM SA-110 at ~162 MHz — the machine that made ARM a serious application processor — with 4 MB of DRAM and a 320×480 monochrome (16-grey) touchscreen. Einstein emulates the whole machine; our build is a thin standalone Emscripten port of its core:

  • StrongARM SA-110 (ARMv4) — the TARMProcessor class, executed by Einstein's portable "Generic" ARM interpreter (no dynarec, so nothing needs writable-executable memory in the browser). This is the CPU the debugger samples; TEmulator::Step() is the run-one-instruction primitive used for single-stepping.
  • Memory & MMUTMemory + TMMU model the ROM (at 0), 4 MB DRAM, the internal flash store (TFlash, a MEMFS-backed image) and the memory-mapped hardware registers (interrupt controller, timers, RTC, DMA, screen, tablet).
  • Newton OS via native primitivesTNativePrimitives implements the platform's flash / screen / tablet / sound / battery / serial drivers that the ROM calls into. Booting the 717006 ROM brings up the real Newton OS 2.1.
  • REXEinstein.rex is the ROM Extension Einstein adds alongside the ROM; both are validated and combined by TFlatROMImageWithREX.
  • Touchscreen — a resistive digitiser over the whole display; pointer events on the canvas drive TScreenManager::PenDown / PenUp.

How to build this exact artefact. Toolchain: Homebrew emscripten (emcc on PATH).

git clone https://github.com/pguyot/einstein.git
# add newton_wasm.cpp (core-driving frontend + newton_dbg_* hooks) and build the
# core (Emulator/*.cpp + JIT/Generic/*.cpp + the K library) MINUS the FLTK/Cocoa/
# SDL/X11/PortAudio backends, with the cooperative-timer patch:
em++ -O2 -std=c++17 -DTARGET_OS_LINUX=1 <core .cpp files> newton_wasm.cpp      -o newton.js -sMODULARIZE=1 -sEXPORT_NAME=Module -sALLOW_MEMORY_GROWTH=1      -sEXPORTED_FUNCTIONS=_newton_boot,_newton_tick,_newton_fb_ptr,_newton_dbg_pc,...      --preload-file 717006 --preload-file Einstein.rex   # -> newton.js + .wasm + .data

libffi (used only by Einstein's host-native-call bridge, never reached in the browser) is stubbed; the pthread-based interrupt/network threads are compiled out by the cooperative-mode patch. The interpreter core runs in plain C++, single-threaded, so it hosts anywhere with no special headers.