Learn how easy it is to sync an existing GitHub or Google Code repo to a SourceForge project! See Demo

Close

Tree [r19] / src /
History



File Date Author Commit
src 2012-08-04 kevodwyer [r13] July 2012
src2 2013-06-15 kevodwyer [r14] June 2013
src3 2015-02-14 kevodwyer [r19] Feb 2015
CHANGES.txt 2015-02-14 kevodwyer [r19] Feb 2015
LicenceGPL2.txt 2011-08-29 kevodwyer [r2]
README.txt 2015-02-14 kevodwyer [r19] Feb 2015
build.zip 2015-02-14 kevodwyer [r19] Feb 2015

Read Me

JavaScript PC Emulator : JsDOSBox
by Kevin O'Dwyer kevodwyer _at_ gmail.com

JavaScript port of jDosbox which is itself a port of DOSBox.  
Built with the assistance of Google Web Toolkit (GWT).

A significant number of changes were required, including but not limited to:
- input event handling
- keyboard key code translation
- output rendering
- threading (lack of)
- date/time/tick handling
- sound mixing and generation.
- unchecked exceptions to checked exceptions (GWT/JS limitation)
- primitive arrays to TypedArrays

and numerous workarounds for various things that don't translate to JavaScript
As with anything that requires R&D, more code was developed and thrown away than actually made it into this codebase.

Project homepage: http://sourceforge.net/projects/jsdosbox/

This program is released under the GPLv2 open source license.
http://www.gnu.org/licenses/gpl-2.0.txt

The following open source projects were used in the making of this project:
jDosbox http://jdosbox.sourceforge.net/	(GPL)
DOSBox	http://www.dosbox.com	(GPL)
gwt-g3d	http://code.google.com/p/gwt-g3d/ (Apache v2)
gwt-nes-port (file upload) http://code.google.com/p/gwt-nes-port/ (GPL v3)  
MIDPath (Zip file handling) http://midpath.thenesis.org/ (GPL)
 - including code from GNU Classpath and phoneME
The Crystal Icon set (config icons) http://www.everaldo.com/crystal/ (LGPL)
Real.java - Java floating point library for MID devices by Roar Lauritzsen (GPL)

HOW TO USE

The first thing to note is that this is a port of DOSBox, so some knowledge is transferable.  
This port is however in beta so not everything DOSBox (or more specifically jDosbox) supports is 
currently available or working.

JsDOSBox has a config using:
- 486 machine type with a FPU
- 8 Mb ram (configurable)
- s3 video card
- sound card emulation

LIMITATIONS
- No network card emulation
- No CD-ROM emulation
- No joystick emulation
- No Midi
- No shell

RUNNING

Extract the jsdosbox.zip file from the base source directory.  
This contains all the static JavaScript files to run JsDOSBox.  
The JsDOSBoxTest.html file includes everything to launch and configure the program. 
Take particular note of the input fields at the end of the file that capture the configuration parameters.  
The files are to be copied into the appropriate directory of your favourite web server.  
REMEMBER:  Due to browser caching, make sure to flush your browser cache as you configure the program.


Configuration parameters:
Very little verification is done on the parameters so be careful.
* If you are reading this section after previously reading it before you will note that some have changed and some have been added/removed.  Defaults have also changed.

    <input id="override" type="hidden" value="false">
If true, a visitor can override the config parameters by appending items to the url query string   
    <input id="frameskip" type="hidden" value="1"> 
values -1 to 6. -1 means auto. -1,0 likely to cause emulator to run slowly. 1,2,3 seem reasonable.  A high number is likely to cause the sound to break up.
    <input id="cycles" type="hidden" value="auto"> 
Indicates cycle strategy. Default is auto.  Values auto, max, limit and ignore. Some programs work best with auto, others work best when a limit is set. It is worth trying one then the other.
    <input id="cyclelimit" type="hidden" value="0"> 
The number of instructions the emulator will try to execute up to per millisecond.  Default value is 0 indicating no limit. Other possible values are from 100 to 90000.  There is usually a sweet spot.  A lower number 3000 - 6000 may make the perceived performance fast but jerky.  A higher number 7000-12000 may mean smoother but slower performance.
    <input id="core" type="hidden" value="dynamic"> 
either dynamic or normal.  Default is dynamic. Normal is likely to have better compatibility.
    <input id="aggressive" type="hidden" value=“true”> 
Only relevant for dynamic core. May improve performance of that core.  No profiling done yet to confirm. 
    <input id="mouse" type="hidden" value=“true”> 
Mouse emulation.  Default value is true
    <input id="maxFPS" type="hidden" value="12"> 
Reduces the number of frames per second to no more then the value set. Default value is 12. Values -1 to 100.
    <input id="memory" type="hidden" value=“12”> 
Amount of main memory.  Default is 8. Max is 24.
    <input id="videomemory" type="hidden" value="12"> 
Amount of onboard memory for the s3 video card.  Default is 2MB. Max is 8.
    <input id="autorun" type="hidden" value="doom.zip -t zip !c:!cd doom!doom.exe"> 

The Autorun parameter captures what gets executed by the emulator.  The '!' character is used as a newline delimiter.  
The first instruction details the name of the file to use and subsequent instructions indicate what to execute.  
The file should be copied into the same directory as the html file.  
The final execution instruction must contain .exe or .com

Currently RAW and zip disk images are supported.  Make sure to append -t zip when using zip files. NOTE: zip files are mounted as read-only.  Attempts by the emulator to write to the zip file will fail!

PC Speaker emulation suffers from constant ticks.  
The SoundBlaster configuration is set to Interrupt 7, DMA 1 and port 220. No midi
Configuration parameters
<input id="nosound" type="hidden" value="false"> 
enable sound.  Default is nosound=false
<input id="sbtype" type="hidden" value="sb1">
options are sb1, sb2, sbpro1, sbpro2, sb16, gb, none. Default is sb1
<input id="oplmode" type="hidden" value="none">
options are auto, none. Default is auto
<input id="gravis" type="hidden" value="false">
enable gravis emulation.  Default is gravis=false
<input id="webaudioenabled" type="hidden" value="false">
Indicates use or otherwise of web audio decodeAudioData. False is default, in which case HTML audio elements are used.
<input id="mixerrate" type="hidden" value="11025">
it is not recommended using a Mixing Freq > 22050 Hz.  A value of 8000 is the current default. Shared by all audio devices.  
Possible values 8000, 11025, 16000, 22050, 32000, 44100
<input id="jsbuffer" type="hidden" value="2048"> 
the size of each sound sample.
<input id="modfactor" type="hidden" value="55">
optional fudge parameter.  This determines how often sound output is generated. The emulator tries to guess
the right interval based on the jsbuffer size and mixerrate.  The lower the value the greater the rate of output generation.
For a 8000khz mixer and a jsbuffer size of 2048, a reasonable modfactor is 55.

Internal sound parameters.  Additional dials to tweak.  The same meaning as the equivalent jdosbox params. 
    <input id="blocksize" type="hidden" value="2048"> 
    <input id="prebuffer" type="hidden" value="40"> 

<input id="backgroundcolor" type="hidden" value=“FFFFFF”>
the color of the page expressed as a hex value. Default is 496E98.
<input id="scalefactor" type="hidden" value=“0”>
scale the canvas. Values -1,1,2,3.  Not all screen resolutions will be scaled.
-1 always draw directly to the canvas.
2,3 always draws to a backing canvas which is then drawn to the scaled screen canvas.
1 indicates a mix of the above. Drawing to the screen directly for non-scaled resolutions and to a backing canvas for scaled resolutions. This is now the default. Scaling can make the output look a bit blurry.
<input id="suppresszipfilewriteerrormsg" type="hidden" value=“false” />
as zip file are mounted as read only and an error message is displayed if an attempt to write is made.  This suppresses the error alert box if set to true.  Default is false.


BUILDING

Built using Google Web Toolkit.  You will need GWT and the GWT eclipse plugin.  
Everything needed to build is under the src directory.  

Extra bits for the brave. . .

Over the course of this years i had various attempts at including a dynrec.  I have long since given up on getting it to work well enough to be useful (look through the history of the source code repo).  I have however reused the jdosbox compiler to spit out java source code for hot code blocks.  These can then be subsequently compiled into the jsdosbox to produce a customised version of jsdosbox to get a bit more performance.  Typical improvement in performance is 30% (higher, if your lucky). If you are not comfortable compiling java sour code then it is not for you. 

Instructions
Append relevant parameters to the query string (make sure you have set the override parameter in the html file)
Jsdosbox.html?verbose=true&numberofclassestosave=200&compilethreshold=10000&minimumblocksize=7
numberofclassestosave - 500 if probably at the upper limit.
compilethreshold - The number of times the code block must be executed before it is a compiled.  The awful hacks i made to the running of the emulator mean that it does not cope well with self-modifying code.  One way to avoid getting bitten is to use a large number.
minimumblocksize - see jdosbox advice.

One way to optimise the params is to watch the output in the web console while running.  On every 25th call to the compiler a line is written to the console. The frequency of the output will help you tune what the params should be. The emulator will likely run erratically during this phase.
jsdosbox.nocache.js:10441 Compiled 25 blocks , ave ops/block: 8.88 T+0m19s

When all is done the java code (yes, Java not Javascript) is written to the console. This then needs to be copied into a file and then parsed to build the actual java source files.  I will probably turn this into a web application next month.

I have attached a suitable program below for parsing chrome browser console output.  Take note of the paths add change to taste.

Then recompile jsdosbox and away you go (do not include the compiling params or change any of the original params used).

For added bonus you can try to inline some methods used in the generated classes.

=======

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

public class Main {

	private static  String FILENAME = "/Users/blah/Desktop/consoleoutput.txt";
	private static  String OUTPUT_DIRECTORY = "/Users/blah/Documents/workspace/jsdosbox/src/net/sourceforge/jsdosbox/client/gen";
	
	public Main()
	{
		System.out.println("INPUT FILE:"+FILENAME);
		System.out.println("OUTPUT DIRECTORY:"+OUTPUT_DIRECTORY);
		if(!OUTPUT_DIRECTORY.endsWith("/")){
			OUTPUT_DIRECTORY = OUTPUT_DIRECTORY + "/";
		}
		System.out.println("starting");
		ArrayList<String> contents = readFile(FILENAME);
		ArrayList<Clazz> clazzes = decode(contents);
		write(clazzes);
		System.out.println("finished");
	}
	private void writeFile(Clazz c)
	{
		BufferedWriter bw = null;
		try{
			bw = new BufferedWriter(new FileWriter(OUTPUT_DIRECTORY+c.getName()));
			bw.write(c.getContents());
			bw.close();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(bw!=null){
				try {
					bw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	private void write(ArrayList<Clazz> clazzes)
	{
		System.out.println("writing "+clazzes.size());
		for(Clazz c : clazzes){
			//System.out.println(c.toString());
			writeFile(c);
		}	
	}

	private static final String END_TOKEN = "} T+";
	private static final String START_TOKEN = "filename:/";
	
	private ArrayList<Clazz> decode(ArrayList<String> contents)
	{
		ArrayList<Clazz> clazzes = new ArrayList<Clazz>();
		StringBuilder classContents = new StringBuilder();
		String name = null;
		boolean newFile=false;
		for(String line : contents){
			//System.out.println(line);
			if(line.contains(START_TOKEN)){
				classContents = new StringBuilder();
				int startIndex = line.indexOf(START_TOKEN) + START_TOKEN.length();
				name = line.substring(startIndex, line.indexOf(" ", startIndex));
				newFile=true;
			}else if(line.startsWith(END_TOKEN)){
				classContents.append("\n}");
				Clazz newClazz = new Clazz(name,classContents.toString());				
				clazzes.add(newClazz);
			}else{
				if(newFile){
					newFile = false;
					line = line.substring(line.indexOf("package"));
				}
				classContents.append("\n"+line);
			}
		}
		System.out.println("read "+clazzes.size());
		return clazzes;
	}
	private void print(ArrayList<String> contents){
		for(String line : contents){
			System.out.println(line);
		}
	}
	private ArrayList<String> readFile(String filename)
	{
		ArrayList<String> contents = new ArrayList<String>();
		BufferedReader br =  null;
		try{
			br = new BufferedReader(new FileReader(filename));
			boolean finished=false;
			while(!finished){
				String line = br.readLine();
				if(line==null){
					finished=true;
				}else{
					contents.add(line);
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(br!=null){
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return contents;
	}
	public static void main(String[] args) {
		new Main();

	}

}


public class Clazz {

	private String name;
	private String contents;
	
	public Clazz(String name,String contents) {
		if(name == null){
			throw new Error("Class name should not be NULL");
		}
		this.name = name;
		this.contents = contents;
	}
	public String getName()
	{
		return name;
	}
	public String getContents()
	{
		return contents;
	}
}