A Simple Hex Editor in JavaScript

The JavaScript code below implements a simple hex editor in JavaScript. To see it in action, copy it into an .html file and open that file in a web browser that runs JavaScript. Or, for an online version, visit https://thiscouldbebetter.neocities.org/hexeditor.html.

hexeditor


<html>
<body>

<div id="divMain"></div>

<script type="text/javascript">

// main

function main()
{
	var session = new Session([]);

	Globals.Instance.initialize(session);
}

// extensions

function StringExtensions()
{
	// extension class
}
{
	String.prototype.padLeft = function(lengthToPadTo, charToPadWith)
	{
		var thisPadded = this;

		while (thisPadded.length < lengthToPadTo)
		{
			thisPadded = 
				charToPadWith + thisPadded;
		}

		return thisPadded;
	}
}

// classes

function Converter()
{
	// static class
}
{
	Converter.PrintableCharacters = 
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		+ "abcdefghijklmnopqrstuvwxyz"
		+ "0123456789";
		// todo - Symbols.

	Converter.bytesToStringASCII = function(bytes)
	{
		var returnValue = "";

		for (var i = 0; i < bytes.length; i++)
		{
			var byte = bytes[i];
			var byteAsCharASCII = String.fromCharCode
			(
				byte
			);

			if (Converter.PrintableCharacters.indexOf(byteAsCharASCII) == -1)
			{
				byteAsCharASCII = ".";	
			}

			returnValue += byteAsCharASCII;
		}

		return returnValue;
	}

	Converter.bytesToStringHexadecimal = function(bytes)
	{
		var returnValue = "";

		for (var i = 0; i < bytes.length; i++)
		{
			var byte = bytes[i];
			var byteAsStringHexadecimal = 
				byte.toString(16).padLeft(2, '0');

			returnValue += byteAsStringHexadecimal;
		}

		return returnValue;
	}

	Converter.stringHexadecimalToBytes = function(stringHexadecimal)
	{
		var returnValues = [];

		var nibblesForByteCurrent = [];

		for (i = 0; i < stringHexadecimal.length; i++)
		{
			var charForNibble = stringHexadecimal[i];
			var nibbleAsInt = parseInt(charForNibble, 16);
			if (isNaN(nibbleAsInt) == false)
			{
				nibblesForByteCurrent.push(nibbleAsInt);
				if (nibblesForByteCurrent.length == 2)
				{
					var byte = 
						(nibblesForByteCurrent[0] << 4) 
						+ nibblesForByteCurrent[1];
					returnValues.push(byte);
					nibblesForByteCurrent.length = 0;
				}
			}			
		}

		return returnValues;
	}	
}

function Globals()
{
	// do nothing
}
{
	Globals.Instance = new Globals();
	
	Globals.prototype.initialize = function(session)
	{
		this.session = session;
		this.session.domElementUpdate();
	}
}

function Session(bytes)
{
	this.bytes = bytes;
	this.finalNibble = "";
}
{
	// dom

	Session.prototype.domElementUpdate = function()
	{
		if (this.domElement == null)
		{
			var divSession = document.createElement("div");

			var textareaHexadecimal = document.createElement("textarea");
			textareaHexadecimal.cols = 40;
			textareaHexadecimal.rows = 10;
			textareaHexadecimal.onkeyup = this.textareaHexadecimal_KeyUp.bind(this);
			textareaHexadecimal.oninput = this.textareaHexadecimal_Changed.bind(this);
			this.textareaHexadecimal = textareaHexadecimal;
			divSession.appendChild(textareaHexadecimal);

			var textareaASCII = document.createElement("textarea");
			textareaASCII.cols = 20;
			textareaASCII.rows = 10;
			textareaASCII.disabled = true;
			this.textareaASCII = textareaASCII;
			divSession.appendChild(textareaASCII);

			var divFileOperations = document.createElement("div");
		
			var buttonSave = document.createElement("button");
			buttonSave.innerHTML = "Save";
			buttonSave.onclick = this.buttonSave_Clicked.bind(this);
			divFileOperations.appendChild(buttonSave);

			var inputFileToLoad = document.createElement("input");
			inputFileToLoad.type = "file";
			inputFileToLoad.onchange = this.inputFileToLoad_Changed.bind(this);
			divFileOperations.appendChild(inputFileToLoad);

			divSession.appendChild(divFileOperations);

			var divCursor = document.createElement("div");
			
			var labelCursorPosition = document.createElement("label");
			labelCursorPosition.innerHTML = "Cursor Position:";
			divCursor.appendChild(labelCursorPosition);

			var inputCursorPosition = document.createElement("input");
			inputCursorPosition.disabled = true;
			this.inputCursorPosition = inputCursorPosition;
			divCursor.appendChild(inputCursorPosition);

			divSession.appendChild(divCursor);

			var divMain = document.getElementById("divMain");
			divMain.appendChild(divSession);

			this.domElement = divSession;
		}

		var bytesAsStringHexadecimal = Converter.bytesToStringHexadecimal
		(
			this.bytes
		);
		this.textareaHexadecimal.value = 
			bytesAsStringHexadecimal + this.finalNibble;

		var bytesAsStringASCII = Converter.bytesToStringASCII
		(
			this.bytes
		);
		this.textareaASCII.value = bytesAsStringASCII;

		var cursorPos = this.textareaHexadecimal.selectionStart;
		var cursorPosAsString = 
			"0d" + cursorPos 
			+ "; 0x" + cursorPos.toString(16)
			+ "; 0b" + cursorPos.toString(2);

		this.inputCursorPosition.value = cursorPosAsString;

		return this.domElement;
	}

	// events

	Session.prototype.buttonSave_Clicked = function()
	{
		var dataAsArrayBuffer = new ArrayBuffer(this.bytes.length);
		var dataAsArrayUnsigned = new Uint8Array(dataAsArrayBuffer);
		for (var i = 0; i < this.bytes.length; i++) 
		{
			dataAsArrayUnsigned[i] = this.bytes[i];
		}
		var dataAsBlob = new Blob([dataAsArrayBuffer], {type:'bytes'});


		var link = document.createElement("a");
		link.href = window.URL.createObjectURL(dataAsBlob);
		link.download = "Data.bin";
		link.click();
	}

	Session.prototype.inputFileToLoad_Changed = function(event)
	{
		var inputFileToLoad = event.target;
		var fileToLoad = inputFileToLoad.files[0];
		if (fileToLoad != null)
		{
	  		var fileReader = new FileReader();
			fileReader.onload = this.inputFileToLoad_Changed_Loaded.bind(this); 
	   		fileReader.readAsBinaryString(fileToLoad);
		}
	}

	Session.prototype.inputFileToLoad_Changed_Loaded = function(fileLoadedEvent) 
	{
		var dataAsBinaryString = fileLoadedEvent.target.result;

		this.bytes = [];

		for (var i = 0; i < dataAsBinaryString.length; i++)
		{
			var byte = dataAsBinaryString.charCodeAt(i);
			this.bytes.push(byte);
		}

		this.domElementUpdate();
	}

	Session.prototype.textareaHexadecimal_Changed = function(event)
	{
		var bytesAsStringHexadecimal = event.target.value;
		this.bytes = Converter.stringHexadecimalToBytes
		(
			bytesAsStringHexadecimal
		);

		if (bytesAsStringHexadecimal.length % 2 == 0)
		{
			this.finalNibble = "";
		}
		else
		{
			this.finalNibble = bytesAsStringHexadecimal.substr
			(
				bytesAsStringHexadecimal.length - 1,
				1
			);

			var finalNibbleAsInt = parseInt(this.finalNibble, 16);
			if (isNaN(finalNibbleAsInt) == true)
			{
				this.finalNibble = "";
			}
		}

		this.domElementUpdate();
	}

	Session.prototype.textareaHexadecimal_KeyUp = function(event)
	{
		if (event.key.indexOf("Arrow") == 0)
		{
			this.domElementUpdate();
		}
	}

}

// run

main();

</script>

</body>
</html>

This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s