Next version of js-dos. API is unstable and incomplete.
DO NOT USE IN PRODUCTION use 6.22 instead.
Subscribe our twitter channel for updates.
Join our discord community.
6.22 is a javascript library that allows you to run DOS programs in browser. js-dos provides nice and easy to use javascript api over dosbox. Try our live examples.
You can found previous version here v3
The fastest way to start with js-dos 6.22 is to use our bootstrap project. You can create simple web page that runs digger in browser with this commands:
npx create-dosbox digger
cd digger
npm install
npm start
--
firefox 127.0.0.1:8080
Scroll down browser window to play Digger, or use fullscreen button under dosbox window.
Controls:
- Arrow keys - move digger (left, right, up, down)
- OR Swipe in direction where you want to move (with mouse too!)
- To enter name use keyboard or keyboard button under dosbox window
To start game, just swipe. Have a nice play!
NOTE: if you have ZIP archive with dos program you can bootstrap it:
npx create-dosbox my-app archive.zip
cd my-app
npm install
npm start
Bootstrap script will create simple html page, that have canvas element. This canvas is used as render surface (output window) to dosbox. Also this page have a js-dos inialization script.
Dos(canvas).ready((fs, main) => {
fs.extract("digger.zip").then(() => {
main(["-c", "DIGGER.COM"])
});
});
It contains this initialization steps:
Dos(canvas)
- will return promise that will be resoled when dosbox is readyready((fs, main) =>)
- will be called when dosbox is ready to runfs
provides API to work with filesystem, we callextract
to mount archive contents as C:main
provides an entry point to run dosbox like in shell you should pass dosbox command line argumentsmain(["-c", "DIGGER.COM"])
means:
dosbox -c DIGGER.COM
Dos has couple configuration options that you can pass as second argument Dos(canvas, options)
.
You can obtain latest build using this (versions):
- js-dos api: https://js-dos.com/6.22/current/js-dos.js
- default version: https://js-dos.com/6.22/current/wdosbox.js
- emterpreter version: https://js-dos.com/6.22/current/wdosbox-emterp.wasm.js
- nosync version: https://js-dos.com/6.22/current/wdosbox-nosync.wasm.js
NOTE: do not try to use this links to serve your copy of dosbox. Because this links always pointing to latest version, and newest version can have breaking changes. Is better to use npx bootstrap command (above), or download latest version from github releases page.
You can use js-dos as npm module.
- Install module package
npm install --save js-dos
- Import Dos module with require
require("js-dos");
This code will automatically inject Dos function into window object. js-dos module also includes typescript sources. In typescript environment you can use it with typechecking:
import { DosFactory } from "js-dos";
require("js-dos");
const Dos = (window as any).Dos as DosFactory;
See example of React component in FAQ section
All examples are interactive, read more in Examples sections.
Demo app that allows try js-dos box with your game archive.
You can found real use of js-dos on certain websites about dos games.
- https://dos.zone/ by @caiiiycuk
- https://js-dos.com/games/ by @caiiiycuk
- https://www.dosgamesarchive.com/play-online/
- https://emupedia.net/beta/ by @ZaDarkSide
- https://www.abandonware-france.org/online
- https://dosgames.com/
- https://sites.google.com/ccboe.us/retrobitrecreation by @YaBoyRetro
- https://sonictruth.github.io/vr-dos/
- https://x.dustinbrett.com/
- https://github.com/xsro/masm-tasm
- https://unchartedwater2.wani.kr/
- https://github.com/sonictruth/vr-dos
- https://boatcad.web.app/
- https://github.com/matthewturk/jupyterlab_dosbox by @powersoffour
- https://www.classicdosgames.com/
- https://www.bestoldgames.net/games/online
- http://nesninja.com/ by @AGB
Please open issue on github if you want to add link for another site that uses js-dos.
To measure performance used variant of Dhrystone 2 Test originally taken from this page. Original version used clock()
to calculate delta time. Which is good for real pc, but does not very accurate for dosbox emulator. When clock()
call happens modified version send ~>dtime
marker which intercepted by test page
and used to calculate delta time with performance.now()
from browser. Source code of modified test is here.
Basically this test produce a lot of int operations and measure amount of operations (Dhrystones) produced per second. Output is a VAX MIPS RATING which is Drhystones per second divided by 1757 (is as DEC VAX 11/780 result).
You can run this test for your PC, change variant and see how different implementations affects performance.
Host for this run is Ubuntu 19.10 Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz. Host result for same test but with native dosbox is 125.
Current version is ~6 times slower then native dosbox. Optimal version to use is a asyncify (wdosbox.js) which have similar performance as a nosync version (wdosbox-nosync.js) but much more stable.
ASM.JS versions emterpJs (wdosbox-emterp.js) and nosyncJs (wdosbox-nosync.js) have better performance only in Firefox, other browser seems does not do any optimization and performance is much worse.
iPhone 8 2.1Ghz | Linux FF i7 - 2.7Ghz | Linux Chrome i7 - 2.7GHz | Xiaomi Mi Max 2 - 2Ghz | Huawei Mate 10 Pro 2.36Ghz | |
---|---|---|---|---|---|
asyncify | 19,83 | 11,2 | 12,14 | 1,89 | 3,07 |
emterp | 18,02 | 7,86 | 8,87 | 1,2 | 2,5 |
nosync | 20,19 | 14,21 | 12,14 | 1,19 | 3,12 |
asyncifyJs | 2,12 | 0,96 | 0,12 | 0,03 | 0,1 |
emterpJs | 0,2 | 7,19 | 2 | 0,84 | 0,88 |
nosyncJs | 0,21 | 9,02 | 1,95 | 0 | 0 |
Read about api provided by js-dos in API Reference
One of the main objective of this project is to bring dosbox on mobile platform. Dosbox it self works in mobile environment without any problem. But old dos programs were made for PC, and often requires keyboard to play. JS-DOS provides additional controls to simulate required hardware through gestures or virtual keyboard.
simple controller that detect swipes in four directions (left, right, up, down) and bind them to keys usually arrow keys. You can use options object to change key bindings.
Dos(canvas).ready((fs, main) => {
main([...]).then((ci) => {
DosController.Move(ci.getParentDiv(), ci.getKeyEventConsumer());
});
});
provides keyboard to enter textual data. Keyboard can be accessed through button that automatically created. You can change button style through options object.
Dos(canvas).ready((fs, main) => {
main([...]).then((ci) => {
DosController.Qwerty(ci.getParentDiv(), ci.getKeyEventConsumer());
});
});
With qwerty controller you can send special characters in Qwerty controller using notation. List of supported names.
allows games to be played via gamepad. It maps gamepad buttons to key strokes.
Dos(canvas).ready((fs, main) => {
main([...]).then((ci) => {
DosController.Gamepad(ci, {
gamepads: [
{
keymap: {
a: 13,
b: 27
},
mapArrows: true, // auto map arrows and sticks
stickThreshold: 0.6 // stick trigger threshold
}
],
scanEvery: 200, // scan input via timer
scanOnTick: true, // scan input on tick
});
});
});
Default gamepad layout is for XBox360 gamepad, it can be changed via buttons parameter:
DosController.Gamepad(ci, {
gamepads: [
{
buttons: [
"a", "b", "x", "y", // 0, 1, 2, 3
"lb", "rb", "lt", "rt", // 4, 5, 6, 7
"back", "start", "N/A", "N/A", // 8, 9, 10, 11
"up", "down", "left", "right" // 12, 13, 14, 15
],
}
],
});
Dosbox will be runned with command line arguments that passed in main function:
Dos(canvas).ready((fs, main) => {
main(["arg1", "arg2", ...])
});
Is equivalent to
dosbox arg1 arg2
You can do this by passing wdosboxUrl
property as second argument:
Dos(canvas, { wdosboxUrl: "/wdosbox.js" }).ready(...);
By changing wdosboxUrl (see above) you can select different dosbox variants:
wdosbox.js
- default variant. This version compiled with latest emscripten and in theory should work best.wdosbox-emterp.js
- This version compiled with legacy fastcomp backend, can be useful in rare cases (e.g. if you have problem with default version).wdosbox-nosync.js
- Fastest possible version, but limited. You can't run console programs/shell emulation using it.dosbox.js
- same as dosbox-emterp.js because default version can't be compiled to asm.jsdosbox-emterp.js
- same as wdosbox-emterp.js but javascript (asm.js)dosbox-nosync.js
- same as wdosbox-nosync.js but javascript (asm.js)
Also you can choose from profiling version of implementation: wdosbox-profiling.js
,
wdosbox-emterp-profiling.js
, wdosbox-nosync-profiling.js
.
Take in account even if you use wasm version of dosbox it will be automatically fallbacked by javascript version if wasm can't start
Default version have limitation and can't be compiled to asm.js, dosbox-emterp.js will be used as fallback for wdosbox.js
Dos(canvas, { wdosboxUrl: "/wdosbox-nosync.js" }).ready(...);
Don't know which version to use, read performance section
You can handle errors by defining onerror
property, or by using catch
of promise.
onerror
style
Dos(canvas, { onerror: (message) => console.error(message); })
catch
style
Dos(canvas)
.ready((fs, main) => ...)
.catch((message) => console.error(message));
NOTE: catch
works only for handling initialization errors
You can
- Override
dosbox.conf
file and write[autoexec]
section, as explained in next section - Or you can simply pass additional command line arguments before you program, like:
main(["-c", "<command>", "-c", "DIGGER.COM"]);
By default js-dos uses builtin dosbox config file.
You have option to change frequently used parameters through DosOptions. For example, you can change cycles
(amount of instructions DOSBox tries to emulate each millisecond):
Dos(canvas, { cycles: 1000, }) // also can be "auto", "max"
// look to DosOptions for other paramters
.ready((fs, main) => ...)
.catch((message) => console.error(message));
To change other parameters you can override it with your config file. To do this you can simply put file named dosbox.conf
inside root of
program archive and then pass command line argument to read it -c dosbox.conf
. Or you can write this file directly from
javascript with fs.createFile.
For example, you can add [autoexec]
section to print dosbox.conf file:
Dos(canvas).ready((fs, main) => {
fs.createFile("dosbox.conf", `
[autoexec]
mount c .
c:
type dosbox~1.con
`);
main(["-conf", "dosbox.conf"]);
});
In that case you need decrease amount of cycles
per second, you can do it through configuration option. Please read
section above.
By default js-dos will show progress of loading dosbox and extracting archives, but you can disable this feature. To do this you need define onprogress handler in DosOptions
Dos(canvas, {
onprogress: (stage, total, loaded) => {
console.log(stage, loaded * 100 / total + "%");
},
}).ready(...);
By default js-dos will log any message using console.log
, and any error with console.error
. To disable this you should
override log
and onerror
property
Dos(canvas, {
log: (message) => { /**/ },
onerror: (message) => { /**/ },
}).ready(...);
You can easily resize canvas by changing css (style) properties width
and height
.
Take attention, you should not change width and height properties of canvas.
<style>
canvas {
width: 1280px;
height: 640px;
}
</style>
<canvas width="640" height="320"></canvas>
Rule is simple: width/height
properties is used by jsdos as native
resolution of game/program, but css width/height
is for changing real size.
So, for giving example resolution will be 640x320, but canvas size will be 1280x640.
NOTE: Currently style attribute inside canvas tag <canvas style="...">
will not affect canvas size because it's under js-dos/emscripten control, and
when dosbox changes video mode this attribute is vanished.
By default dosbox mouse will follow browser cursor without locking. It means that js-dos will not take exclusive control over mouse pointer. However you can change this behaviour by providing autolock=true
in
dosbox.conf
or through DosOptions. Then js-dos will take exclusive control and lock mouse inside js-dos container (you can't leave it). This will happen after first click, and you can unlock mouse by pressing CTRL+F10
or ESC
.
Dos(canvas, { autolock: true }).ready((fs, main) => {
main([...]);
});
** OR **
Dos(canvas).ready((fs, main) => {
fs.createFile("dosbox.conf", `
[sdl]
autolock=true
`);
main(["-conf", "dosbox.conf"]);
});
JsDos provides very basic support for entering fullscreen mode. You can request to enter fullscreen mode using CommandInterface.fullscreen().
<button onclick="ci.fullscreen()">Fullscreen</button>
<button onclick="ci.exitFullscreen()">Exit fullscreen</button>
<script>
Dos(canvas).ready((fs, main) => {
main([...]).then((ci) => {
window.ci = ci;
});
});
</script>
To exit fullscreen use CommandInterface.exitFullscreen().
NOTE: This function can be called anywhere, but for web security reasons its associated request can only be raised inside the event handler for a user-generated event (for example a key, mouse or touch press/release).
NOTE2: Native fullscreen have limited support in some browser. In case when browser does not support Fullscreen API, css workaround will be used.
JsDos already support multiple instances, just create new canvas for each jsdos and instaniate it normally. Like in this 64k demoscene example.
js-dos file system is in memory file system. It means that every file that you exracted, or file that created by game (save file, config, etc.) will be lost on browser restart. However it's possible to create directory in file system that will be synced and stored inside indexed db. This type of folder will store it's content across browser restarts. You can create as many folders as you want, all of them will be synced.
But, usually only one folder per game needed. So, simplies way to store game progress is
just extract game archive to different folder (not root /
). For example:
Dos(canvas).ready((fs, main) => {
fs.extract("game.zip", "/game").then(() => {
main(["-c", "cd game", "-c", "game.exe"])
});
});
As you can see second argument in extract
method is a path where to extract contents archive, and this path will be automatically mount as persistent (because it's not root /
).
In other words to store game progress just extract game archive into some folder, and that is.
**NOTE 1: ** Some dos games save game progress when you exit them. If you just close browser before exiting game, some games will lost porgress.
**NOTE 2: ** Do not forget to change directory to correct path before running game.
**NOTE 3: ** Because content of folder is stored in indexed db original archive is downloaded and extracted only once to avoid rewriting stored content! This means that you can't update it from archive, and of course you can't store different content (from different archives) into one path.
Save game files are stored inside a browser's indexed db database. If you wish to transfer these files to another machine (or make a backup) you must first store them in a normal file on a file system.
Here is an example how you can achieve that. When you click an 'export' button these will write all the files in dos file system matched by reg-ex /SAVEGAME/ to a file on local file system. This file can then be copied to another machine, where it can be imported via 'import' button.
Dos(canvas).ready((fs, main) => {
fs.extract("game.zip", "/game").then(() => {
main(["-c", "cd game", "-c", "game.exe"]).then((ci) => {
function exportState() {
let filename = "game_" + new Date().getTime() + ".state";
fs.writeFsToFile(filename, /SAVEGAME/, "/game");
}
function importState() {
if (confirm("This will overwrite your current state. Are you sure?")) {
let importInput = document.getElementById('importStateInput');
if (!importInput.oninput) {
importInput.oninput = function (e) {
fs.readFsFromFile(e.target.files[0]);
alert("State was imported successfully!");
};
}
importInput.click();
}
}
let exportButton = document.getElementById("exportState");
exportButton.addEventListener("click", exportState);
let importButton = document.getElementById("importState");
importButton.addEventListener("click", importState);
}
});
});
This section requires that you read section above. Sometimes need to have multiple persistent folders to run single game.
For example, one for sound drivers another for game it self. It can be done with extractAll
method. It accepts array of sources
to extract and extract them in one time. Actually, extract
method is just syntax shugar and implemented via extractAll
.
Dos(canvas).ready((fs, main) => {
fs.extractAll([
{ url: "ultrsound.zip", mountPoint: "/snd" },
{ url: "game.zip", mountPoint: "/game" },
}).then(() => {
main(["-c", "cd game", "-c", "game.exe"])
});
});
No need to cache js-dos scripts, because they are automatically added to indexed db cache, so from every second load js-dos can work in offline mode.
When your program is runned you can take screenshot with special CommandInterface.screenshot(). That is returned by main promise:
Dos(canvas).ready((fs, main) => {
fs.extract("digger.zip").then(() => {
main(["-c", "DIGGER.COM"]).then((ci) => {
ci.screenshot().then((data) => {
const w = window.open("about:blank", "image from canvas");
w.document.write("<img src='" + data + "' alt='from canvas'/>");
});
});
});
});
Data is an ImageData from canvas object
Sometimes needed to stop execution of dosbox to free resources or whatever. You can do this with CommandInterface.exit():
Dos(canvas).ready((fs, main) => {
fs.extract("digger.zip").then(() => {
main(["-c", "DIGGER.COM"]).then((ci) => {
ci.exit(); // Will stop execution immediately
});
});
});
This comonent is only demostrate how you can use js-dos with React framework.
import React, { useRef, useEffect } from "react";
import { DosFactory } from "js-dos";
require("js-dos");
const Dos = (window as any).Dos as DosFactory;
const JsDos: React.FC = () => {
const ref = useRef<HTMLCanvasElement>(null);
useEffect(() => {
if (ref !== null) {
const ciPromise = Dos(ref.current as HTMLCanvasElement, {
wdosboxUrl: "https://js-dos.com/6.22/current/wdosbox.js",
}).then((runtime) => {
return runtime.fs.extract("https://js-dos.com/6.22/current/test/digger.zip").then(() => {
return runtime.main(["-c", "DIGGER.COM"]);
});
});
return () => {
ciPromise.then(ci => ci.exit());
};
}
}, [ref]);
return <canvas ref={ref} />;
}
export default JsDos;
Then you can use it as simple html tag <JsDos />
Normally when you use js-dos in iframe keyboard should work. But remember that iframe must be focused
to receive keyboard events. By default when you click on iframe it recives focus, but if you use
mouse event listener with prevent default at the end, then iframe will not recieve focus. You can solve
this problem by calling iframe.focus()
function.
You can easily simulate key press and release using CommandInterface.simulateKeyEvent(keyCode: number, pressed: boolean). Or you can use shortcut for single event simulateKeyPress(keyCode: number)
, like in example:
Dos(canvas).ready((fs, main) => {
fs.extract("digger.zip").then(() => {
main(["-c", "DIGGER.COM"]).then((ci) => {
ci.simulateKeyPress(37); // left arrow
})
});
});
Using babel notation: > 0.25%, not dead, ie >= 11
Building process have two steps:
- You need to build emulation layer (dosbox)
- You need to build API
Project uses dosbox as emulation layer for running dos programs. You should build it before building javascript API. To do this you should have emscripten installed in your system and CMake. Build process should be easy if you familar with cmake, just run this commands:
// install emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emskd
./emsdk install 1.39.4
./emsdk activate 1.39.4
source ./emsdk_env.sh
emcc --clear-cache
// set to llvm backend
mkdir build
cd build
emcmake cmake ..
make wdosbox dosbox wdosbox-nosync dosbox-nosync
// set to fastcomp backend
mkdir build-emterp
cd build-emterp
emcmake cmake ..
make wdosbox-emterp dosbox-emterp
You can build javascript API with gulp, just type gulp.
// requires node 11.8.0
// install gulp
npm install
npm install [email protected]
gulp
Output will be placed in dist folder. Also in dist folder you can find test page, you open it in browser. All test should pass.
firefox dist/test/test.html
Additionaly you can run same tests on other variants of js-dos (emterp, nosync). BUT, not all tests will pass.
firefox dist/test/test-js.html
firefox dist/test/test-emterp.html
firefox dist/test/test-js-emterp.html
firefox dist/test/test-nosync.html
firefox dist/test/test-js-nosync.html