|
|
@@ -5,6 +5,41 @@ import NESTile from "/app/js/models/NESTile.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){ |
|
|
|
if (isNaN(lid) || lid < 0 || lid > 2){ |
|
|
|
lid = 2; |
|
|
@@ -31,6 +66,7 @@ function LRIdx2TileIdxCo(index, lid){ |
|
|
|
return res; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export default class NESBank extends ISurface{ |
|
|
|
constructor(){ |
|
|
|
super(); |
|
|
@@ -40,18 +76,41 @@ export default class NESBank extends ISurface{ |
|
|
|
this.__AccessMode = NESBank.ACCESSMODE_8K; |
|
|
|
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"); |
|
|
|
} |
|
|
|
}).bind(this), 250); |
|
|
|
|
|
|
|
for (var i=0; i < 256; i++){ |
|
|
|
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[i].listen("data_changed", handle_datachanged.bind(this, 1)); |
|
|
|
this.__RP[i].listen("data_changed", handle_datachanged.bind(this, 1, i)); |
|
|
|
} |
|
|
|
|
|
|
|
this.__palette = null; |
|
|
@@ -62,19 +121,86 @@ export default class NESBank extends ISurface{ |
|
|
|
if (!Utils.isInt(m)) |
|
|
|
throw new TypeError("Access mode expected to be integer."); |
|
|
|
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; |
|
|
|
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; |
|
|
|
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; |
|
|
|
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(){ |
|
|
@@ -85,20 +211,56 @@ export default class NESBank extends ISurface{ |
|
|
|
} |
|
|
|
|
|
|
|
get chr(){ |
|
|
|
var buff = new Uint8Array(8192); |
|
|
|
var buff = null; |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
set chr(buff){ |
|
|
|
/*set chr(buff){ |
|
|
|
if (!(buff instanceof Uint8Array)) |
|
|
|
throw new TypeError("Expected Uint8Array buffer."); |
|
|
|
if (buff.length !== 8192) |
|
|
@@ -112,7 +274,7 @@ export default class NESBank extends ISurface{ |
|
|
|
i.chr = buff.slice(offset, offset+15); |
|
|
|
offset += 16; |
|
|
|
}); |
|
|
|
} |
|
|
|
}*/ |
|
|
|
|
|
|
|
get base64(){ |
|
|
|
var b = ""; |
|
|
@@ -141,8 +303,16 @@ export default class NESBank extends ISurface{ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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 coloridx(){ |
|
|
@@ -156,10 +326,12 @@ export default class NESBank extends ISurface{ |
|
|
|
prop = parseInt(prop); |
|
|
|
if (prop < 0 || prop >= len) |
|
|
|
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){ |
|
|
@@ -173,10 +345,12 @@ export default class NESBank extends ISurface{ |
|
|
|
throw new RangeError("Index out of bounds."); |
|
|
|
if (value < 0 || value >= 4) |
|
|
|
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; |
|
|
|
} |
|
|
|
}); |
|
|
@@ -250,14 +424,92 @@ export default class NESBank extends ISurface{ |
|
|
|
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){ |
|
|
|
if (x < 0 || x >= this.width || y < 0 || y >= this.height) |
|
|
|
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){ |
|
|
|
return this.__palette.get_palette_color(pi, ci); |
|
|
@@ -269,11 +521,11 @@ export default class NESBank extends ISurface{ |
|
|
|
if (x < 0 || x >= this.width || y < 0 || y >= this.height) |
|
|
|
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 { |
|
|
|
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) |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
@@ -289,11 +541,11 @@ export default class NESBank extends ISurface{ |
|
|
|
this.coloridx[(y*this.width)+x] = ci; |
|
|
|
} 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; |
|
|
|
} |