| import NESPalette from "/app/js/models/NESPalette.js"; | import NESPalette from "/app/js/models/NESPalette.js"; | ||||
| function CnvIdx(x, y, am, off){ | |||||
| var res = { | |||||
| side: 0, | |||||
| tileidx: 0, | |||||
| x: 0, | |||||
| y: 0 | |||||
| } | |||||
| switch(am){ | |||||
| case NESBank.ACCESSMODE_8K: | |||||
| res.side = (x > 128) ? 1 : 0; | |||||
| x -= (res.side === 1) ? 128, 0; | |||||
| res.tileidx = (Math.floor(y/8) * 16) + Math.floor(x / 8); | |||||
| break; | |||||
| case NESBank.ACCESSMODE_4K: | |||||
| res.side = off; | |||||
| res.tileidx = (Math.floor(y/8) * 16) + Math.floor(x / 8); | |||||
| break; | |||||
| case NESBank.ACCESSMODE_2K: | |||||
| res.side = Math.floor(off * 0.5); | |||||
| res.tileidx = (res.side*32) + ((Math.floor(y/8) * 16) + Math.floor(x / 8)); | |||||
| break; | |||||
| case NESBank.ACCESSMODE_1K: | |||||
| res.side = Math.floor(off * 0.25); | |||||
| off -= (off > 3) ? 4 : 0; | |||||
| res.tileidx = (off * 16) + ((Math.floor(y/8) * 16) + Math.floor(x / 8)); | |||||
| break; | |||||
| } | |||||
| res.x = x%8; | |||||
| res.y = y%8; | |||||
| return res; | |||||
| } | |||||
| function LRIdx2TileIdxCo(index, lid){ | function LRIdx2TileIdxCo(index, lid){ | ||||
| if (isNaN(lid) || lid < 0 || lid > 2){ | if (isNaN(lid) || lid < 0 || lid > 2){ | ||||
| lid = 2; | lid = 2; | ||||
| return res; | return res; | ||||
| } | } | ||||
| export default class NESBank extends ISurface{ | export default class NESBank extends ISurface{ | ||||
| constructor(){ | constructor(){ | ||||
| super(); | super(); | ||||
| this.__AccessMode = NESBank.ACCESSMODE_8K; | this.__AccessMode = NESBank.ACCESSMODE_8K; | ||||
| this.__AccessOffset = 0; | this.__AccessOffset = 0; | ||||
| var handle_datachanged = Utils.debounce((function(side){ | |||||
| if ((side == 0 && (this.__AccessMode == 0 || this.__AccessMode == 2)) || | |||||
| (side == 1 && (this.__AccessMode == 1 || this.__AccessMode == 2))){ | |||||
| this.__emitsEnabled = true; | |||||
| var handle_datachanged = Utils.debounce((function(side, idx){ | |||||
| var sendEmit = false; | |||||
| switch(this.__AccessMode){ | |||||
| case NESBank.ACCESSMODE_1K: | |||||
| if (side === Math.floor(this.__AccessOffset / 4){ | |||||
| if (Math.floor(idx / 64) === Math.floor(this.__AccessOffset/4)) | |||||
| sendEmit = true; | |||||
| } | |||||
| break; | |||||
| case NESBank.ACCESSMODE_2K: | |||||
| if (side === Math.floor(this.__AccessOffset / 2){ | |||||
| if (Math.floor(idx / 128) === Math.floor(this.__AccessOffset/2)) | |||||
| sendEmit = true; | |||||
| } | |||||
| break; | |||||
| case NESBank.ACCESSMODE_4K: | |||||
| if (side === this.__AccessOffset) | |||||
| sendEmit = true; | |||||
| break; | |||||
| case NESBank.ACCESSMODE_8K: | |||||
| sendEmit = true; | |||||
| } | |||||
| if (sendEmit && this.__emitsEnabled){ | |||||
| this.emit("data_changed"); | this.emit("data_changed"); | ||||
| } | } | ||||
| }).bind(this), 250); | }).bind(this), 250); | ||||
| for (var i=0; i < 256; i++){ | for (var i=0; i < 256; i++){ | ||||
| this.__LP.push(new NESTile()); | this.__LP.push(new NESTile()); | ||||
| this.__LP[i].listen("data_changed", handle_datachanged.bind(this, 0)); | |||||
| this.__LP[i].listen("data_changed", handle_datachanged.bind(this, 0, i)); | |||||
| this.__RP.push(new NESTile()); | this.__RP.push(new NESTile()); | ||||
| this.__RP[i].listen("data_changed", handle_datachanged.bind(this, 1)); | |||||
| this.__RP[i].listen("data_changed", handle_datachanged.bind(this, 1, i)); | |||||
| } | } | ||||
| this.__palette = null; | this.__palette = null; | ||||
| if (!Utils.isInt(m)) | if (!Utils.isInt(m)) | ||||
| throw new TypeError("Access mode expected to be integer."); | throw new TypeError("Access mode expected to be integer."); | ||||
| switch(m){ | switch(m){ | ||||
| case NESBank.ACCESSMODE_SPRITE: | |||||
| this.__AccessMode = NESBank.ACCESSMODE_SPRITE; | |||||
| this.emit("data_changed"); | |||||
| case NESBank.ACCESSMODE_8K: | |||||
| this.__AccessMode = NESBank.ACCESSMODE_8K; | |||||
| this.__AccessOffset = 0; | |||||
| break; | break; | ||||
| case NESBank.ACCESSMODE_BACKGROUND: | |||||
| this.__AccessMode = NESBank.ACCESSMODE_BACKGROUND; | |||||
| this.emit("data_changed"); | |||||
| case NESBank.ACCESSMODE_4K: | |||||
| if (this.__AccessOffset > 1){ | |||||
| switch(this.__AccessMode){ | |||||
| case NESBank.ACCESSMODE_2K: | |||||
| this.__AccessOffset = Math.floor(this.__AccessOffset * 0.5); | |||||
| break; | |||||
| case NESBank.ACCESSMODE_1K: | |||||
| this.__AccessOffset = Math.floor(this.__AccessOffset * 0.25); | |||||
| break; | |||||
| } | |||||
| } | |||||
| this.__AccessMode = NESBank.ACCESSMODE_4K | |||||
| break; | break; | ||||
| case NESBank.ACCESSMODE_FULL: | |||||
| this.__AccessMode = NESBank.ACCESSMODE_FULL; | |||||
| this.emit("data_changed"); | |||||
| case NESBank.ACCESSMODE_2K: | |||||
| switch(this.__AccessMode){ | |||||
| case NESBank.ACCESSMODE_8K: | |||||
| this.__AccessOffset = 0; | |||||
| break; | |||||
| case NESBank.ACCESSMODE_4K: | |||||
| this.__AccessOffset *= 2; | |||||
| break; | |||||
| case NESBank.ACCESSMODE_1K: | |||||
| this.__AccessOffset = Math.floor(this.__AccessOffset * 0.5); | |||||
| break; | |||||
| } | |||||
| this.__AccessMode = NESBank.ACCESSMODE_2K; | |||||
| break; | break; | ||||
| case NESBank.ACCESSMODE_1K: | |||||
| switch(this.__AccessMode){ | |||||
| case NESBank.ACCESSMODE_8K: | |||||
| this.__AccessOffset = 0; | |||||
| break; | |||||
| case NESBank.ACCESSMODE_4K: | |||||
| this.__AccessOffset *= 4; | |||||
| break; | |||||
| case NESBank.ACCESSMODE_2K: | |||||
| this.__AccessOffset *= 2; | |||||
| break; | |||||
| } | |||||
| this.__AccessMode = NESBank.ACCESSMODE_1K; | |||||
| break; | |||||
| default: | |||||
| throw new ValueError("Unknown Access Mode."); | |||||
| } | } | ||||
| if (this.__emitsEnabled) | |||||
| this.emit("data_changed"); | |||||
| } | |||||
| get access_offset(){return this.__AccessOffset;} | |||||
| set access_offset(o){ | |||||
| if (!Utils.isInt(m)) | |||||
| throw new TypeError("Access offset expected to be integer."); | |||||
| switch (this.__AccessMode){ | |||||
| case NESBank.ACCESSMODE_8K: | |||||
| if (o !== 0) | |||||
| throw new RangeError("Access Offset is out of bounds based on current Access Mode."); | |||||
| break; | |||||
| case NESBank.ACCESSMODE_4K: | |||||
| if (o !== 0 && o !== 1) | |||||
| throw new RangeError("Access Offset is out of bounds based on current Access Mode."); | |||||
| break; | |||||
| case NESBank.ACCESSMODE_2K: | |||||
| if (o < 0 || o >= 4) | |||||
| throw new RangeError("Access Offset is out of bounds based on current Access Mode."); | |||||
| break; | |||||
| case NESBank.ACCESSMODE_1K: | |||||
| if (o < 0 || o >= 8) | |||||
| throw new RangeError("Access Offset is out of bounds based on current Access Mode."); | |||||
| break; | |||||
| } | |||||
| this.__AccessOffset = m; | |||||
| if (this.__emitsEnabled) | |||||
| this.emit("data_changed"); | |||||
| } | } | ||||
| get json(){ | get json(){ | ||||
| } | } | ||||
| get chr(){ | get chr(){ | ||||
| var buff = new Uint8Array(8192); | |||||
| var buff = null; | |||||
| var offset = 0; | var offset = 0; | ||||
| this.__LP.forEach(function(i){ | |||||
| buff.set(i.chr, offset); | |||||
| offset += 16; | |||||
| }); | |||||
| this.__RP.forEach(function(i){ | |||||
| buff.set(i.chr, offset); | |||||
| offset += 16; | |||||
| }); | |||||
| switch (this.__AccessMode){ | |||||
| case NESBank.ACCESSMODE_8K: | |||||
| buff = new Uint8Array(8192); | |||||
| this.__LP.forEach((i) => { | |||||
| buff.set(i.chr, offset); | |||||
| offset += 16; | |||||
| }); | |||||
| this.__RP.forEach((i) => { | |||||
| buff.set(i.chr, offset); | |||||
| offset += 16; | |||||
| }); | |||||
| break; | |||||
| case NESBank.ACCESSMODE_4K: | |||||
| buff = new Uint8Array(4096); | |||||
| var list = (this.__AccessOffset === 0) ? this.__LP : this.__RP; | |||||
| list.forEach((i) => { | |||||
| buff.set(i.chr, offset); | |||||
| offset += 16; | |||||
| }); | |||||
| break; | |||||
| case NESBank.ACCESSMODE_2K: | |||||
| buff = new Uint8Array(2048); | |||||
| var list = (this.__AccessOffset < 2) ? this.__LP : this.__RP; | |||||
| var s = Math.floor(this.__AccessOffset * 0.5) * 128; | |||||
| var e = s + 128; | |||||
| for (let i=s; i < e; i++){ | |||||
| buff.set(list[i].chr, offset); | |||||
| offset += 16; | |||||
| } | |||||
| break; | |||||
| case NESBank.ACCESSMODE_1K: | |||||
| buff = new Uint8Array(1024); | |||||
| var list = (this.__AccessOffset < 4) ? this.__LP : this.__RP; | |||||
| var s = Math.floor(this.__AccessOffset * 0.25) * 64; | |||||
| var e = s + 64; | |||||
| for (let i=s; i < e; i++){ | |||||
| buff.set(list[i].chr, offset); | |||||
| offset += 16; | |||||
| } | |||||
| break; | |||||
| } | |||||
| return buff; | return buff; | ||||
| } | } | ||||
| set chr(buff){ | |||||
| /*set chr(buff){ | |||||
| if (!(buff instanceof Uint8Array)) | if (!(buff instanceof Uint8Array)) | ||||
| throw new TypeError("Expected Uint8Array buffer."); | throw new TypeError("Expected Uint8Array buffer."); | ||||
| if (buff.length !== 8192) | if (buff.length !== 8192) | ||||
| i.chr = buff.slice(offset, offset+15); | i.chr = buff.slice(offset, offset+15); | ||||
| offset += 16; | offset += 16; | ||||
| }); | }); | ||||
| } | |||||
| }*/ | |||||
| get base64(){ | get base64(){ | ||||
| var b = ""; | var b = ""; | ||||
| } | } | ||||
| } | } | ||||
| get width(){return (this.__AccessMode == NESBank.ACCESSMODE_FULL) ? 256 : 128;} | |||||
| get height(){return 128;} | |||||
| get width(){return (this.__AccessMode == NESBank.ACCESSMODE_8K) ? 256 : 128;} | |||||
| get height(){ | |||||
| switch(this.__AccessMode){ | |||||
| case NESBank.ACCESSMODE_2K: | |||||
| return 64; | |||||
| case NESBank.ACCESSMODE_1K: | |||||
| return 32; | |||||
| } | |||||
| return 128; | |||||
| } | |||||
| get length(){return this.width * this.height;} | get length(){return this.width * this.height;} | ||||
| get coloridx(){ | get coloridx(){ | ||||
| prop = parseInt(prop); | prop = parseInt(prop); | ||||
| if (prop < 0 || prop >= len) | if (prop < 0 || prop >= len) | ||||
| return NESPalette.Default[4]; | return NESPalette.Default[4]; | ||||
| var res = LRIdx2TileIdxCo(prop, this.__AccessMode); | |||||
| var list = (res.lid === 0) ? obj.__LP : obj.__RP; | |||||
| return list[res.index].getPixelIndex(res.x, res.y); | |||||
| var x = Math.floor(prop % this.width); | |||||
| var y = Math.floor(prop / this.width); | |||||
| var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset); | |||||
| var list = (res.side === 0) ? obj.__LP : obj.__RP; | |||||
| return list[res.tileidx].getPixelIndex(res.x, res.y); | |||||
| }, | }, | ||||
| set:function(obj, prop, value){ | set:function(obj, prop, value){ | ||||
| throw new RangeError("Index out of bounds."); | throw new RangeError("Index out of bounds."); | ||||
| if (value < 0 || value >= 4) | if (value < 0 || value >= 4) | ||||
| throw new RangeError("Color index out of bounds."); | throw new RangeError("Color index out of bounds."); | ||||
| var res = LRIdx2TileIdxCo(prop, this.__AccessMode); | |||||
| var list = (res.lid === 0) ? obj.__LP : obj.__RP; | |||||
| list[res.index].setPixelIndex(res.x, res.y, value); | |||||
| var x = Math.floor(prop % this.width); | |||||
| var y = Math.floor(prop / this.width); | |||||
| var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset); | |||||
| var list = (res.side === 0) ? obj.__LP : obj.__RP; | |||||
| list[res.tileidx].setPixelIndex(res.x, res.y, value); | |||||
| return true; | return true; | ||||
| } | } | ||||
| }); | }); | ||||
| return (new NESBank()).copy(this); | return (new NESBank()).copy(this); | ||||
| } | } | ||||
| getCHR(mode, offset){ | |||||
| this.__emitsEnabled = false; | |||||
| var oam = this.access_mode; | |||||
| var oao = this.access_offset; | |||||
| try{ | |||||
| this.access_mode = mode; | |||||
| this.access_offset = offset; | |||||
| } catch (e){ | |||||
| this.access_mode = oam; | |||||
| this.access_offset = oao; | |||||
| this.__emitsEnabled = true; | |||||
| throw e; | |||||
| } | |||||
| var chr = this.chr; | |||||
| this.access_mode = oam; | |||||
| this.access_offset = oao; | |||||
| this.__emitsEnabled = true; | |||||
| return chr; | |||||
| } | |||||
| setCHR(buff, offset){ | |||||
| if (!Utils.isInt(offset) || offset < 0) | |||||
| offset = 0; | |||||
| var idx = 0; | |||||
| switch(buff.length){ | |||||
| case 8192: | |||||
| this.__LP.forEach((i) => { | |||||
| i.chr = buff.slice(idx, idx+15); | |||||
| idx += 16; | |||||
| }); | |||||
| this.__RP.forEach((i) => { | |||||
| i.chr = buff.slice(idx, idx+15); | |||||
| idx += 16; | |||||
| }); | |||||
| break; | |||||
| case 4096: | |||||
| if (offset >= 2) | |||||
| throw new RangeError("Offset mismatch based on Buffer length."); | |||||
| var list = (offset === 0) ? this.__LP : this.__RP; | |||||
| list.forEach((i) => { | |||||
| i.chr = buff.slice(idx, idx+15); | |||||
| idx += 16; | |||||
| }); | |||||
| break; | |||||
| case 2048: | |||||
| if (offset >= 4) | |||||
| throw new RangeError("Offset mismatch based on Buffer length."); | |||||
| var list = (offset < 2) ? this.__LP : this.__RP; | |||||
| var s = Math.floor(offset * 0.5) * 128; | |||||
| var e = s + 128; | |||||
| for (let i=s; i < e; i++){ | |||||
| list[i].chr = buff.slice(idx, idx+15); | |||||
| idx += 16; | |||||
| } | |||||
| break; | |||||
| case 1024: | |||||
| if (offset >= 8) | |||||
| throw new RangeError("Offset mismatch based on Buffer length."); | |||||
| var list = (offset < 4) ? this.__LP : this.__RP; | |||||
| var s = Math.floor(this.__AccessOffset * 0.25) * 64; | |||||
| var e = s + 64; | |||||
| for (let i=s; i < e; i++){ | |||||
| list[i].chr = buff.slice(idx, idx+15); | |||||
| idx += 16; | |||||
| } | |||||
| break; | |||||
| default: | |||||
| throw new RangeError("Buffer length does not match any of the supported bank sizes."); | |||||
| } | |||||
| return this; | |||||
| } | |||||
| getColor(x,y){ | getColor(x,y){ | ||||
| if (x < 0 || x >= this.width || y < 0 || y >= this.height) | if (x < 0 || x >= this.width || y < 0 || y >= this.height) | ||||
| return this.__default_pi[4]; | return this.__default_pi[4]; | ||||
| var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode); | |||||
| var list = (res.lid === 0) ? this.__LP : this.__RP; | |||||
| var pi = list[res.index].paletteIndex + ((res.lid === 0) ? 4 : 0); | |||||
| var ci = list[res.index].getPixelIndex(res.x, res.y); | |||||
| var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset); | |||||
| var list = (res.side === 0) ? this.__LP : this.__RP; | |||||
| var pi = list[res.tileidx].paletteIndex + ((res.side === 0) ? 4 : 0); | |||||
| var ci = list[res.tileidx].getPixelIndex(res.x, res.y); | |||||
| if (this.__palette !== null){ | if (this.__palette !== null){ | ||||
| return this.__palette.get_palette_color(pi, ci); | return this.__palette.get_palette_color(pi, ci); | ||||
| if (x < 0 || x >= this.width || y < 0 || y >= this.height) | if (x < 0 || x >= this.width || y < 0 || y >= this.height) | ||||
| return {pi: -1, ci:-1}; | return {pi: -1, ci:-1}; | ||||
| var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode); | |||||
| var list = (res.lid === 0) ? this.__LP : this.__RP; | |||||
| var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset); | |||||
| var list = (res.side === 0) ? this.__LP : this.__RP; | |||||
| return { | return { | ||||
| pi: list[res.index].paletteIndex, | |||||
| ci: list[res.index].getPixelIndex(res.x, res.y) | |||||
| pi: list[res.tileidx].paletteIndex, | |||||
| ci: list[res.tileidx].getPixelIndex(res.x, res.y) | |||||
| }; | }; | ||||
| } | } | ||||
| this.coloridx[(y*this.width)+x] = ci; | this.coloridx[(y*this.width)+x] = ci; | ||||
| } else { | } else { | ||||
| var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode); | |||||
| var list = (res.lid === 0) ? this.__LP : this.__RP; | |||||
| var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset); | |||||
| var list = (res.side === 0) ? this.__LP : this.__RP; | |||||
| list[res.index].paletteIndex = pi; | |||||
| list[res.index].setPixelIndex(res.x, res.y, ci); | |||||
| list[res.tileidx].paletteIndex = pi; | |||||
| list[res.tileidx].setPixelIndex(res.x, res.y, ci); | |||||
| } | } | ||||
| return this; | return this; | ||||
| } | } |