Today | News | Books | Recipes Notes | QuickTake | Wiki | Browse Maps | Reference | Reddit | YouTube Chat | Games | About https://nullprogram.com/blog/2026/09/17/ Open Original Page <!DOCTYPE html>
<title>A custom virtual machine for the Stars! 4X game</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="alternate" type="application/atom+xml" href="/feed/" title="Atom Feed"/>
<link rel="pgpkey" type="application/pgp-keys" href="/0xAFD1503A8C8FF42A.pgp"/>
<link rel="stylesheet" href="/css/full.css"/> <main lang="en">
<article class="single">
<h2><a href="/blog/2026/09/17/">A custom virtual machine for the Stars! 4X game</a></h2>
<time datetime="2026-09-17">
September 17, 2026
</time>
<div class="print-only url">
nullprogram.com/blog/2026/09/17/
</div> <p><a href="https://en.wikipedia.org/wiki/Stars!">Stars!</a> is a 1995 <a href="https://en.wikipedia.org/wiki/4X">4X</a> game (explore, expand, exploit, exterminate)
for 16-bit Windows 3.1 that I first played ~28 years ago. While Windows is
famously backwards compatible, it's notoriously difficult to play Stars!
today. Windows x64 cannot run 16-bit applications, and playing requires
either retro hardware or emulation (<a href="https://github.com/otya128/winevdm">otvdm</a>, <a href="https://www.dosbox.com/">DOSBox</a>), sometimes
paired with <a href="https://www.winehq.org/">Wine</a>. My new, exciting solution, <strong><a href="https://github.com/skeeto/StarsVM">Stars!VM</a></strong>, or
<em>Stars! Virtual Machine</em>, embeds a custom <a href="/blog/2014/12/09/">80286</a> emulator and a Win16
to Win32 bridge. As native Win32, the game looks and feels exactly as it
did originally, except sporting a modern file chooser and 4k scaling. It's
indistinguishable from a genuine 32-bit or 64-bit port of the game,
especially with the original 16-bit game embedded inside the VM
executable.</p> <p><img src="/img/stars.png" alt="" /></p> <p>The <a href="/blog/2026/04/25/">signed</a> releases on GitHub embed a compressed copy of the original
16-bit game, so that single EXE is ready to play out-of-the-box with no
further setup or downloads. I'm distributing 32-bit builds (but requires
SSE2) because there's no advantage to 64-bit here, and these builds work
(almost) everywhere <em>except</em> 16-bit Windows. 32-bit Windows could run the
original 16-bit game, but the VM-encapsulated version is better behaved.
It doesn't dump a <code class="language-plaintext highlighter-rouge">Stars.ini</code> under <code class="language-plaintext highlighter-rouge">C:\WINDOWS</code>, it interacts properly
with the task bar, and copy protection is neutralized via the OS bridge.</p> <p>If you ever been curious about Stars!, now's the time to try it. The game
has a thorough, built-in tutorial, but also check out the <a href="https://wiki.starsautohost.org/">wiki</a>, the
<a href="https://starsautohost.org/strategy/guidef/SSG.htm">official strategy guide</a>, and <a href="https://starsautohost.org/stars.htm">AutoHost</a> (play-by-email service).
The game predates the modern search engine concept, otherwise they might
have chosen a better name. I suggest using "stars 4x" in your searches.</p> <p>If you want to build from source and hack on the VM yourself, the best
tool for the job is <a href="https://github.com/skeeto/w64devkit">w64devkit</a>, of course, because it <a href="/blog/2020/09/25/">comes with
everything you'll need</a>. Plus the game itself: <code class="language-plaintext highlighter-rouge">stars.exe</code>
from <a href="https://wiki.starsautohost.org/wiki/Downloads"><code class="language-plaintext highlighter-rouge">stars27jrc3.zip</code></a>.</p> <h3 id="implementation-details">Implementation details</h3> <p>The emulator itself requires x86 or x86-64 because it does not implement
x87 (80-bit floating point) in software, but instead runs these operation
directly on the host's x87 hardware. This is simple, fast, and precise.
The project validates the emulation as a whole with a differential fuzzer
against the host. The fuzzer randomly generates a 16-bit instruction,
emulates it, then runs it with <a href="/blog/2015/03/19/">JIT</a> on the host and compares the
results.</p> <p>Handles on Windows are pointer-sized, and so the Win16-to-Win32 bridge
maps 16-bit handles to host handles. It marshals between different struct
layouts when translating these calls, services the DOS interrupts the game
requires, an copies data in and out of guest memory. It's rather like
running a <a href="/blog/2026/01/01/">Wasm</a> instance, which of course makes sense in retrospect.</p> <p>The Win32 bridge is also monitorable and manipulatable using the Model
Context Protocol (MCP). AI agents can "see" the UI "DOM" as it's built,
and can drive it by injecting synthetic events into the event pump, all
without going through the usual desktop control. The MCP can also read and
write guest memory. Opus 5 played a complete game through MCP - which is
quite fun to watch - requesting my assistance at just two points when it
got stuck in the UI. A foundation for a new Stars!Bench?</p> <p>Targeting old 16-bit computers, the authors couldn't afford to build a
sloppy, wasteful UI, and so by modern standards the game UI is remarkably
fast and responsive. They don't make 'em like they used to. Computing the
next turn, or "turn generation," is the computational bottleneck, and so
that's where I focused my optimization efforts. The emulator can trace
executed instructions, so I gathered traces of turn generation, then had
Fable 5.1 identify and reverse engineer the hottest common routines (e.g.
the game's L'Ecuyer MCG PRNG) and basic blocks. Each was re-written in C
and mapped into the instruction decoder as new 80286 instructions. On load
the emulator identifies these routines and patches them with the new
instruction. This resulted in a nearly ~2x speedup of turn generation. By
exploiting local conditions, emulating a particular known program, I get
JIT performance without JIT complexity.</p> <p>The original game doesn't use <a href="/blog/2023/02/13/">buffered I/O</a>, and instead issues many
small reads and writes. Passing these small reads/writes straight to Win32
made I/O take ~5% turn generation time, probably worse today than it was
back then. Plus it's just rude. The emulator buffers the game's I/O calls,
further speeding up turn generation.</p> <p>The game has some sound effects in the "battle VCR" and the final version
of the game shipped with Microsoft's <code class="language-plaintext highlighter-rouge">WaveMix.dll</code>. Rather than load and
link this DLL, the emulator implements the DLL's interfaces natively, and
these routines are dynamically linked into the 16-bit process. You will
not need this DLL with the emulator, nor is it embedded in releases.</p> <p>The game also has art assets embedded uncompressed in the original EXE,
forming the bulk of its ~3MB. Stars!VM uses a custom LZ-based compression
algorithm tailored to compressing the original game. It's compressed when
embedded in releases. So the 32-bit version of the game is half the size
of the original, at ~1.5MB.</p> <p>The original game requires a serial code, serving as its copy protection.
A code is 8 alpha-numeric characters that must pass two checks. Failing
the first is loud, but failing the second will sabotage your game with
penalties. You'll know because it will announce that your people suspect
you are a usurper. Most codes you'll find online are such "usurper" codes.
Play-by-email (PBEM) saves embed a hardware signature derived from C and D
drive configuration. People playing on different machines using the same
serial code recieve the usurper penalty.</p> <p>I bought a serial code back in the day, but it hasn't been possible to
purchase one a for at least decade now. So the emulator injects a fixed
serial code on first run (disable with <code class="language-plaintext highlighter-rouge">--prompt-serial</code>), and you won't
need to worry about it. The VM also produces a fixed hardware signature
(same as any other emulator), so it looks like everyone running Stars!VM
is sharing the a machine, meaning no penalty for key reuse. I cracked the
serial code checks anyway, allowing me discover interesting ones. My
favorites: CLONEMUM, CROSSNUT, EGGSWAIN, GHOSTKIN, GONKSHOW, GRIPMIME,
SEEKCAPS, SIFTBOLD, SIRBUOYS, SLIMFAZE, SLOTMOPS, SPAWNELK, SUNBLOND,
WANTNEAR, and WRONGPOX. These look like some of <a href="/blog/2017/07/27/">my passwords</a>.</p> <h3 id="endless-possibilities">Endless possibilities</h3> <p>I'm quite pleased and excited with the results, especially for a weekend
project. It's breathed life back into the game for me, not only having a
better experience running it, but also that I can trivially bend the game
to my will in the ways I dreamed about. A few hooks in the right places
should open the game to easy modding, but I'm more engineer than modder.</p> <ul class="tags">
<li><a href="/tags/c/">c</a></li>
<li><a href="/tags/game/">game</a></li>
</ul>
<ol class="references print-only"></ol> <div class="no-print comments">
<p>Have a comment on this article? Start a discussion in my
<a href="https://lists.sr.ht/~skeeto/public-inbox">public inbox</a>
by sending an email to
<a href="mailto:~skeeto/public-inbox@lists.sr.ht?Subject=Re%3A%20A%20custom%20virtual%20machine%20for%20the%20Stars!%204X%20game">
~skeeto/public-inbox@lists.sr.ht
</a>
<span class="etiquette">
[<a href="https://man.sr.ht/lists.sr.ht/etiquette.md">mailing list etiquette</a>]
</span>,
or see
<a href="https://lists.sr.ht/~skeeto/public-inbox?search=A+custom+virtual+machine+for+the+Stars%21+4X+game">existing discussions</a>.
</p>
</div> <nav class="no-print">
<div class="prev">
<span class="marker">«</span>
<a href="/blog/2026/05/06/">
Concurrent, atomic MSI hash tables
</a>
</div>
<div class="next">
<span class="marker">»</span>
<a href="/blog/2026/09/20/">
What's been going on in w64devkit the past year
</a>
</div>
</nav>
</article> </main> <header>
<div class="container">
<div class="portrait identity"></div>
<h1 class="site-title identity"><a href="/">null program</a></h1>
<h2 class="full-name identity">Chris Wellons</h2>
<address class="identity">
<div><a id="email" href="mailto:wellons@nullprogram.com">wellons@nullprogram.com</a> (<a rel="publickey" type="application/pgp-keys" href="/0xAFD1503A8C8FF42A.pgp">PGP</a>)</div>
<div><a id="public-inbox" href="mailto:~skeeto/public-inbox@lists.sr.ht">~skeeto/public-inbox@lists.sr.ht</a> (<a href="https://lists.sr.ht/~skeeto/public-inbox">view</a>)</div>
</address>
<nav>
<ul>
<li class="nav index"><a href="/index/">Index</a></li>
<li class="nav tags"><a href="/tags/">Tags</a></li>
<li class="nav feed"><a href="/feed/">Feed</a></li>
<li class="nav about"><a href="/about/">About</a></li>
<li class="nav tools"><a href="/tools/">Tools</a></li>
<li class="nav toys"><a href="/toys/">Toys</a></li>
<li class="nav github"><a href="https://github.com/skeeto">GitHub</a></li>
</ul>
</nav>
</div>
</header> <footer>
<p>
All information on this blog, unless otherwise noted, is
hereby released into the public domain, with no rights
reserved.
</p>
</footer>
Browse another page: |