Skip to content

DosKit: Running DOS Software in Modern Browsers with WebAssembly

Explore DosKit, a cross-platform foundation for running DOS applications using js-dos WebAssembly technology. Learn about emulation architecture, browser compatibility, and preserving computing history through modern web standards.

DosKit: Running DOS Software in Modern Browsers with WebAssembly

The golden age of DOS computing produced software that defined a generation of computer users. From groundbreaking demos to productivity applications, this software represents an important chapter in computing history. DosKit provides a modern foundation for experiencing this legacy directly in web browsers, leveraging WebAssembly to run DOS binaries with remarkable fidelity.

The Preservation Imperative

DOS software faces an existential threat. As original hardware fails and operating systems evolve, the ability to run these programs diminishes. Browser-based emulation offers a compelling solution: instant access without installation, cross-platform compatibility, and the permanence of web standards.

DosKit builds on js-dos, a WebAssembly port of DOSBox, to provide a robust runtime environment. The architecture abstracts the complexity of emulation setup while exposing configuration options for advanced users.

An abstract diagram illustrating the translation of raw DOS binaries through the WebAssembly engine into smooth browser execution.

WebAssembly: The Enabling Technology

WebAssembly makes browser-based DOS emulation practical by providing near-native execution speed:

async function initializeDosKit(containerElement, programUrl) {
  const bundle = await Dos(containerElement);
  const instance = await bundle.run(programUrl);
  
  return {
    instance,
    sendKey: (key) => instance.sendKeyEvent(key, true),
    setSpeed: (cycles) => instance.setConfig({ cycles })
  };
}

The compiled DOSBox core executes at speeds sufficient for even demanding DOS software, including action games and complex demos.

Cross-Platform Consistency

One of DosKit’s primary goals is consistent behavior across platforms:

const platformConfig = {
  mobile: {
    touchControls: true,
    virtualKeyboard: true,
    audioContext: 'user-gesture-required'
  },
  desktop: {
    touchControls: false,
    fullscreenSupport: true,
    keyboardCapture: true
  }
};

function detectPlatform() {
  const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
  return isMobile ? platformConfig.mobile : platformConfig.desktop;
}

Mobile devices receive touch controls and virtual keyboards, while desktop browsers get full keyboard capture and enhanced fullscreen support.

Audio Handling Challenges

Browser audio policies require careful handling. Modern browsers block autoplay, requiring user interaction before audio can begin:

class AudioManager {
  constructor() {
    this.context = null;
    this.initialized = false;
  }
  
  async initialize() {
    if (this.initialized) return;
    
    this.context = new AudioContext();
    if (this.context.state === 'suspended') {
      await this.context.resume();
    }
    this.initialized = true;
  }
}

// Initialize on first user interaction
document.addEventListener('click', () => {
  audioManager.initialize();
}, { once: true });

This pattern ensures audio works reliably while respecting browser security policies.

File System Abstraction

DOS programs expect a filesystem. DosKit provides virtual filesystem support:

async function mountFilesystem(instance, files) {
  for (const [path, content] of Object.entries(files)) {
    await instance.fs.writeFile(path, content);
  }
}

// Example: Mount a configuration file
await mountFilesystem(dosInstance, {
  '/CONFIG.SYS': 'FILES=40\nBUFFERS=25',
  '/AUTOEXEC.BAT': '@ECHO OFF\nPATH C:\\;C:\\DOS'
});

This abstraction enables loading programs from URLs, IndexedDB, or user uploads while presenting a familiar DOS environment.

Performance Tuning

DOS software varies dramatically in resource requirements. DosKit provides configuration options:

const performanceProfiles = {
  '8086': { cycles: 300, type: 'real' },
  '286': { cycles: 3000, type: 'real' },
  '386': { cycles: 8000, type: 'real' },
  '486': { cycles: 25000, type: 'real' },
  'max': { cycles: 'max', type: 'auto' }
};

function applyPerformanceProfile(instance, profile) {
  const config = performanceProfiles[profile];
  instance.setConfig({
    cycles: config.cycles,
    cycleType: config.type
  });
}

Cycle-accurate emulation ensures software runs at authentic speeds, important for games with timing-dependent mechanics.

A smartphone screen running a retro game with a visible virtual joystick overlay, highlighting mobile compatibility.

Touch Controls for Mobile

Mobile support requires virtual input devices:

class VirtualJoystick {
  constructor(container) {
    this.element = document.createElement('div');
    this.element.className = 'virtual-joystick';
    container.appendChild(this.element);
    
    this.bindTouchEvents();
  }
  
  bindTouchEvents() {
    this.element.addEventListener('touchmove', (e) => {
      const touch = e.touches[0];
      const rect = this.element.getBoundingClientRect();
      const x = (touch.clientX - rect.left) / rect.width;
      const y = (touch.clientY - rect.top) / rect.height;
      
      this.emitDirection(x, y);
    });
  }
  
  emitDirection(x, y) {
    // Convert position to arrow key presses
    if (x < 0.3) this.sendKey('ArrowLeft');
    if (x > 0.7) this.sendKey('ArrowRight');
    if (y < 0.3) this.sendKey('ArrowUp');
    if (y > 0.7) this.sendKey('ArrowDown');
  }
}

These controls make DOS software accessible on devices that never existed during the DOS era.

State Preservation

Save states enable users to pause and resume sessions:

async function saveState(instance) {
  const state = await instance.saveState();
  const blob = new Blob([state], { type: 'application/octet-stream' });
  
  // Store in IndexedDB for persistence
  await stateStorage.save('last-session', blob);
}

async function loadState(instance) {
  const blob = await stateStorage.load('last-session');
  if (blob) {
    const state = await blob.arrayBuffer();
    await instance.loadState(state);
  }
}

This feature transforms ephemeral browser sessions into persistent experiences.

Conclusion

DosKit demonstrates that computing history need not be locked away in museums or abandoned to bit rot. WebAssembly provides the performance necessary for faithful emulation, while modern web APIs enable rich, cross-platform experiences. The result is immediate, barrier-free access to software that shaped the computing landscape.


Experience DOS classics at doskit.net or explore the source at github.com/cameronrye/doskit.