...and add it all back.

This commit is contained in:
RHY3756547 committed 2017-09-08 17:24:16 +01:00
1 parent 41b2b7455d
commit 2064315dea
95 files changed
+19964

No files matched your search

+158
View File
@@ -0,0 +1,158 @@
//
// nsbtx.js
//--------------------
// Reads NSBTX files (or TEX0 sections) and provides canvases containing decoded texture data.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js
//
window.nsbtx = function(input, tex0, autogen) {
var texDataSize, texInfoOff, texOffset, compTexSize, compTexInfoOff,
compTexOffset, compTexInfoDataOff /*wtf*/, palSize, palInfoOff,
palOffset, mainOff
var textureInfo, paletteInfo, palData, texData, colourBuffer
var thisObj = this;
var bitDepths = [0, 8, 2, 4, 8, 2, 8, 16]
if (input != null) {
load(input, tex0, autogen);
}
this.load = load;
this.readTexWithPal = readTexWithPal;
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(input, tex0, autogen) {
var colourBuffer = new Uint32Array(4);
var view = new DataView(input);
var header = null;
var offset = 0;
if (!tex0) { //nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "BTX0") throw "NSBTX invalid. Expected BTX0, found "+header.stamp;
if (header.numSections > 1) throw "NSBTX invalid. Too many sections - should only have one.";
offset = header.sectionOffsets[0];
}
mainOff = offset;
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (stamp != "TEX0") throw "NSBTX invalid. Expected TEX0, found "+stamp;
var size = view.getUint32(offset+0x04, true);
var texDataSize = view.getUint16(offset+0x0C, true)<<8;
var texInfoOff = view.getUint16(offset+0x0E, true);
var texOffset = view.getUint16(offset+0x14, true);
var compTexSize = view.getUint16(offset+0x1C, true)<<8;
var compTexInfoOff = view.getUint16(offset+0x1E, true);
var compTexOffset = view.getUint32(offset+0x24, true);
var compTexInfoDataOff = view.getUint32(offset+0x28, true);
var palSize = view.getUint32(offset+0x30, true)<<3;
var palInfoOff = view.getUint32(offset+0x34, true);
var palOffset = view.getUint32(offset+0x38, true);
//read palletes, then textures.
palData = input.slice(mainOff + palOffset, palSize);
texData = input.slice(mainOff + texOffset, texDataSize);
paletteInfo = nitro.read3dInfo(view, palInfoOff, palInfoHandler);
textureInfo = nitro.read3dInfo(view, texInfoOff, texInfoHandler);
/*if (f) {
console.log(textureInfo.objectData.length)
for (var i=0; i<textureInfo.objectData.length; i++) {
var canvas = readTexWithPal(i, textureInfo.objectData[i].pal)
document.body.appendChild(canvas);
console.log("uh")
}
}*/
}
function readTexWithPal(textureId, palId) {
palView = new DataView(palData);
texView = new DataView(texData);
var tex = textureInfo.objectData[textureId];
var pal = paletteInfo.objectData[palId];
var bit = bitDepths[pal.format]; //todo, compressed format whatever that is
var canvas = document.createElement("canvas");
canvas.width = tex.width;
canvas.height = tex.height;
var ctx = canvas.getContext("2d");
var data = ctx.getImageData(0, 0, tex.width, tex.height);
var off = tex.texOffset
var palOff = pal.palOffset;
var total = tex.width*tex.height;
var databuf = texView.readUint8(off);
for (var i=0; i<total; i++) {
var col;
if (bitDepths == 2) {
col = readPalColour(palView, palOff, (databuf>>((i%4)*2))&3)
if (i%4 == 3) databuf = texView.readUint8(++off);
} else if (bitDepths == 4) {
if (i%2 == 0) {
col = readPalColour(palView, palOff, databuf&15)
} else {
col = readPalColour(palView, palOff, databuf>>4)
databuf = texView.readUint8(++off);
}
} else if (bitDepths == 8) {
col = readPalColour(palView, palOff, texView.readUint8(off))
off += 1;
} else if (bitDepths == 16) {
col = readPalColour(palView, palOff, texView.readUint16(off, true))
off += 2;
}
data.data.set(col, i*4);
}
return canvas;
}
function readPalColour(view, palOff, ind) {
var col = palView.getUint16(palOff+ind*2, true);
colourBuffer[0] = Math.round(((col&31)/31)*255)
colourBuffer[1] = Math.round((((col>>5)&31)/31)*255)
colourBuffer[2] = Math.round((((col>>10)&31)/31)*255)
colourBuffer[3] = Math.round((col>>15)*255)
}
function palInfoHandler(view, offset) {
var palOffset = view.getUint16(offset, true)<<3;
var unknown = view.getUint16(offset+2, true);
return {
palOffset: palOffset,
nextoff: offset+4
}
}
function texInfoHandler(view, offset) {
var texOffset = view.getUint16(offset, true)<<3;
var flags = view.getUint16(offset+2, true);
var width2 = view.getUint8(offset+4, true);
var unknown = view.getUint8(offset+5, true);
var height2 = view.getUint8(offset+6, true);
var unknown2 = view.getUint8(offset+7, true);
return {
texOffset: texOffset,
pal: (flags>>13),
format: ((flags>>10)&7),
height: ((flags>>7)&7)<<3,
width: ((flags>>4)&7)<<3,
nextoff: offset+8
}
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+158
View File
@@ -0,0 +1,158 @@
//
// nsbtx.js
//--------------------
// Reads NSBTX files (or TEX0 sections) and provides canvases containing decoded texture data.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js
//
window.nsbtx = function(input, tex0, autogen) {
var texDataSize, texInfoOff, texOffset, compTexSize, compTexInfoOff,
compTexOffset, compTexInfoDataOff /*wtf*/, palSize, palInfoOff,
palOffset, mainOff
var textureInfo, paletteInfo, palData, texData, colourBuffer
var thisObj = this;
var bitDepths = [0, 8, 2, 4, 8, 2, 8, 16]
if (input != null) {
load(input, tex0, autogen);
}
this.load = load;
this.readTexWithPal = readTexWithPal;
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(input, tex0, autogen) {
var colourBuffer = new Uint32Array(4);
var view = new DataView(input);
var header = null;
var offset = 0;
if (!tex0) { //nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "BTX0") throw "NSBTX invalid. Expected BTX0, found "+header.stamp;
if (header.numSections > 1) throw "NSBTX invalid. Too many sections - should only have one.";
offset = header.sectionOffsets[0];
}
mainOff = offset;
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (stamp != "TEX0") throw "NSBTX invalid. Expected TEX0, found "+stamp;
var size = view.getUint32(offset+0x04, true);
var texDataSize = view.getUint16(offset+0x0C, true)<<8;
var texInfoOff = view.getUint16(offset+0x0E, true);
var texOffset = view.getUint16(offset+0x14, true);
var compTexSize = view.getUint16(offset+0x1C, true)<<8;
var compTexInfoOff = view.getUint16(offset+0x1E, true);
var compTexOffset = view.getUint32(offset+0x24, true);
var compTexInfoDataOff = view.getUint32(offset+0x28, true);
var palSize = view.getUint32(offset+0x30, true)<<3;
var palInfoOff = view.getUint32(offset+0x34, true);
var palOffset = view.getUint32(offset+0x38, true);
//read palletes, then textures.
palData = input.slice(mainOff + palOffset, palSize);
texData = input.slice(mainOff + texOffset, texDataSize);
paletteInfo = nitro.read3dInfo(view, palInfoOff, palInfoHandler);
textureInfo = nitro.read3dInfo(view, texInfoOff, texInfoHandler);
if (autogen) {
console.log(textureInfo.objectData.length)
for (var i=0; i<textureInfo.objectData.length; i++) {
var canvas = readTexWithPal(i, textureInfo.objectData[i].pal)
document.body.appendChild(canvas);
console.log("uh")
}
}
}
function readTexWithPal(textureId, palId) {
palView = new DataView(palData);
texView = new DataView(texData);
var tex = textureInfo.objectData[textureId];
var pal = paletteInfo.objectData[palId];
var bit = bitDepths[pal.format]; //todo, compressed format whatever that is
var canvas = document.createElement("canvas");
canvas.width = tex.width;
canvas.height = tex.height;
var ctx = canvas.getContext("2d");
var data = ctx.getImageData(0, 0, tex.width, tex.height);
var off = tex.texOffset
var palOff = pal.palOffset;
var total = tex.width*tex.height;
var databuf = texView.readUint8(off);
for (var i=0; i<total; i++) {
var col;
if (bitDepths == 2) {
col = readPalColour(palView, palOff, (databuf>>((i%4)*2))&3)
if (i%4 == 3) databuf = texView.readUint8(++off);
} else if (bitDepths == 4) {
if (i%2 == 0) {
col = readPalColour(palView, palOff, databuf&15)
} else {
col = readPalColour(palView, palOff, databuf>>4)
databuf = texView.readUint8(++off);
}
} else if (bitDepths == 8) {
col = readPalColour(palView, palOff, texView.readUint8(off))
off += 1;
} else if (bitDepths == 16) {
col = readPalColour(palView, palOff, texView.readUint16(off, true))
off += 2;
}
data.data.set(col, i*4);
}
return canvas;
}
function readPalColour(view, palOff, ind) {
var col = palView.getUint16(palOff+ind*2, true);
colourBuffer[0] = Math.round(((col&31)/31)*255)
colourBuffer[1] = Math.round((((col>>5)&31)/31)*255)
colourBuffer[2] = Math.round((((col>>10)&31)/31)*255)
colourBuffer[3] = Math.round((col>>15)*255)
}
function palInfoHandler(view, offset) {
var palOffset = view.getUint16(offset, true)<<3;
var unknown = view.getUint16(offset+2, true);
return {
palOffset: palOffset,
nextoff: offset+4
}
}
function texInfoHandler(view, offset) {
var texOffset = view.getUint16(offset, true)<<3;
var flags = view.getUint16(offset+2, true);
var width2 = view.getUint8(offset+4, true);
var unknown = view.getUint8(offset+5, true);
var height2 = view.getUint8(offset+6, true);
var unknown2 = view.getUint8(offset+7, true);
return {
texOffset: texOffset,
pal: (flags>>13),
format: ((flags>>10)&7),
height: ((flags>>7)&7)<<3,
width: ((flags>>4)&7)<<3,
nextoff: offset+8
}
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+158
View File
@@ -0,0 +1,158 @@
//
// nsbtx.js
//--------------------
// Reads NSBTX files (or TEX0 sections) and provides canvases containing decoded texture data.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js
//
window.nsbtx = function(input, tex0, autogen) {
var texDataSize, texInfoOff, texOffset, compTexSize, compTexInfoOff,
compTexOffset, compTexInfoDataOff /*wtf*/, palSize, palInfoOff,
palOffset, mainOff
var textureInfo, paletteInfo, palData, texData, colourBuffer
var thisObj = this;
var bitDepths = [0, 8, 2, 4, 8, 2, 8, 16]
if (input != null) {
load(input, tex0, autogen);
}
this.load = load;
this.readTexWithPal = readTexWithPal;
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(input, tex0, autogen) {
var colourBuffer = new Uint32Array(4);
var view = new DataView(input);
var header = null;
var offset = 0;
if (!tex0) { //nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "BTX0") throw "NSBTX invalid. Expected BTX0, found "+header.stamp;
if (header.numSections > 1) throw "NSBTX invalid. Too many sections - should only have one.";
offset = header.sectionOffsets[0];
}
mainOff = offset;
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (stamp != "TEX0") throw "NSBTX invalid. Expected TEX0, found "+stamp;
var size = view.getUint32(offset+0x04, true);
var texDataSize = view.getUint16(offset+0x0C, true)<<8;
var texInfoOff = view.getUint16(offset+0x0E, true);
var texOffset = view.getUint16(offset+0x14, true);
var compTexSize = view.getUint16(offset+0x1C, true)<<8;
var compTexInfoOff = view.getUint16(offset+0x1E, true);
var compTexOffset = view.getUint32(offset+0x24, true);
var compTexInfoDataOff = view.getUint32(offset+0x28, true);
var palSize = view.getUint32(offset+0x30, true)<<3;
var palInfoOff = view.getUint32(offset+0x34, true);
var palOffset = view.getUint32(offset+0x38, true);
//read palletes, then textures.
palData = input.slice(mainOff + palOffset, palSize);
texData = input.slice(mainOff + texOffset, texDataSize);
paletteInfo = nitro.read3dInfo(view, palInfoOff, palInfoHandler);
textureInfo = nitro.read3dInfo(view, texInfoOff, texInfoHandler);
if (false) {
console.log(textureInfo.objectData.length)
for (var i=0; i<textureInfo.objectData.length; i++) {
var canvas = readTexWithPal(i, textureInfo.objectData[i].pal)
document.body.appendChild(canvas);
console.log("uh")
}
}
}
function readTexWithPal(textureId, palId) {
palView = new DataView(palData);
texView = new DataView(texData);
var tex = textureInfo.objectData[textureId];
var pal = paletteInfo.objectData[palId];
var bit = bitDepths[pal.format]; //todo, compressed format whatever that is
var canvas = document.createElement("canvas");
canvas.width = tex.width;
canvas.height = tex.height;
var ctx = canvas.getContext("2d");
var data = ctx.getImageData(0, 0, tex.width, tex.height);
var off = tex.texOffset
var palOff = pal.palOffset;
var total = tex.width*tex.height;
var databuf = texView.readUint8(off);
for (var i=0; i<total; i++) {
var col;
if (bitDepths == 2) {
col = readPalColour(palView, palOff, (databuf>>((i%4)*2))&3)
if (i%4 == 3) databuf = texView.readUint8(++off);
} else if (bitDepths == 4) {
if (i%2 == 0) {
col = readPalColour(palView, palOff, databuf&15)
} else {
col = readPalColour(palView, palOff, databuf>>4)
databuf = texView.readUint8(++off);
}
} else if (bitDepths == 8) {
col = readPalColour(palView, palOff, texView.readUint8(off))
off += 1;
} else if (bitDepths == 16) {
col = readPalColour(palView, palOff, texView.readUint16(off, true))
off += 2;
}
data.data.set(col, i*4);
}
return canvas;
}
function readPalColour(view, palOff, ind) {
var col = palView.getUint16(palOff+ind*2, true);
colourBuffer[0] = Math.round(((col&31)/31)*255)
colourBuffer[1] = Math.round((((col>>5)&31)/31)*255)
colourBuffer[2] = Math.round((((col>>10)&31)/31)*255)
colourBuffer[3] = Math.round((col>>15)*255)
}
function palInfoHandler(view, offset) {
var palOffset = view.getUint16(offset, true)<<3;
var unknown = view.getUint16(offset+2, true);
return {
palOffset: palOffset,
nextoff: offset+4
}
}
function texInfoHandler(view, offset) {
var texOffset = view.getUint16(offset, true)<<3;
var flags = view.getUint16(offset+2, true);
var width2 = view.getUint8(offset+4, true);
var unknown = view.getUint8(offset+5, true);
var height2 = view.getUint8(offset+6, true);
var unknown2 = view.getUint8(offset+7, true);
return {
texOffset: texOffset,
pal: (flags>>13),
format: ((flags>>10)&7),
height: ((flags>>7)&7)<<3,
width: ((flags>>4)&7)<<3,
nextoff: offset+8
}
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+71
View File
@@ -0,0 +1,71 @@
//
// kartoffsetdata.js
//--------------------
// Provides functionality to read mario kart ds kart wheel and character model offsets.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
//
window.kartoffsetdata = function(input) {
var thisObj = this;
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var off = 0;
var karts = []
for (var i=0; i<37; i++) {
var obj = {};
obj.name = readString(view, off, 0x10);
off += 0x10;
obj.frontTireSize = view.getInt32(off, true)/4096;
off += 4;
var wheels = [];
for (var j=0; j<4; j++) {
var pos = vec3.create();
pos[0] = view.getInt32(off, true)/4096;
pos[1] = view.getInt32(off+4, true)/4096;
pos[2] = view.getInt32(off+8, true)/4096;
off += 12;
wheels.push(pos);
}
var chars = [];
for (var j=0; j<13; j++) {
var pos = vec3.create();
pos[0] = view.getInt32(off, true)/4096;
pos[1] = view.getInt32(off+4, true)/4096;
console.log("charPos: "+pos[1]);
pos[2] = view.getInt32(off+8, true)/4096;
off += 12;
chars.push(pos);
}
obj.wheels = wheels;
obj.chars = chars;
karts.push(obj);
}
thisObj.karts = karts;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
function readString(view, offset, length) {
var str = "";
for (var i=0; i<length; i++) {
var b = view.getUint8(offset++);
if (b != 0) str += String.fromCharCode(b);
}
return str;
}
}
+65
View File
@@ -0,0 +1,65 @@
//
// kartphysicalparam.js
//--------------------
// Provides functionality to read mario kart ds kart physical parameters
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0) (maybe)
//
window.kartphysicalparam = function(input) {
var thisObj = this;
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var off = 0;
var karts = []
for (var i=0; i<50; i++) {
var obj = {};
var colParam = [];
obj.colRadius = view.getInt32(off, true)/4096;
obj.unknown1 = view.getInt32(off+0x4, true)/4096;
obj.unknown2 = view.getInt32(off+0x8, true)/4096;
obj.weight = view.getInt16(off+0xC, true)/4096;
obj.miniTurbo = view.getUint16(off+0xE, true);
obj.topSpeed = view.getInt32(off+0x10, true)/4096;
obj.accel1 = view.getInt32(off+0x14, true)/4096;
obj.accel2 = view.getInt32(off+0x18, true)/4096;
obj.accelSwitch = view.getInt32(off+0x1C, true)/4096;
obj.driftAccel1 = view.getInt32(off+0x20, true)/4096;
obj.driftAccel2 = view.getInt32(off+0x24, true)/4096;
obj.driftAccelSwitch = view.getInt32(off+0x28, true)/4096;
obj.decel = view.getInt32(off+0x2C, true)/4096;
obj.turnRate = (view.getInt16(off+0x30, true)/32768)*Math.PI;
obj.driftTurnRate = (view.getInt16(off+0x32, true)/32768)*Math.PI;
obj.driftOffRestore = (view.getInt16(off+0x34, true)/32768)*Math.PI;
obj.unknown3 = view.getInt16(off+0x36, true);
var off1 = off+0x38;
var off2 = off+0x68;
for (var j=0; j<12; j++) {
var handling = view.getInt32(off1, true)/4096;
var topSpeed = view.getInt32(off2, true)/4096;
colParam.push({
handling: handling,
topSpeedMul: topSpeed
});
off1+=4;
off2+=4;
}
obj.colParam = colParam;
karts.push(obj);
off += 0x98;
}
thisObj.karts = karts;
}
}
+295
View File
@@ -0,0 +1,295 @@
//
// kcl.js
//--------------------
// Loads kcl files and provides a variety of functions for accessing and using the data.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
//
window.kcl = function(input, mkwii) {
//todo, support versions for other games (MKWii etc)
this.load = load;
this.getPlanesAt = getPlanesAt;
var vertexOffset, normalOffset, planeOffset, octreeOffset, unknown1, topLeftVec,
xMask, yMask, zMask, coordShift, yShift, zShift, unknown2, trisMapped = 0,
//decoded data
planes, octree, end, mkwiiMode //little endian for ds, big endian for wii
var sf, mouseX = 0, mouseY = 0, offx, offz, loaded = false //for testing
var Fixed32Point = 4096;
if (input != null) {
//handle input, load kcl from data
if (typeof input == "string") {
var xml = new XMLHttpRequest();
xml.responseType = "arraybuffer";
xml.open("GET", input, true);
xml.onload = function() {
load(xml.response);
}
xml.send();
} else {
load(input, mkwii);
}
}
window.onmousemove = function(evt) {
mouseX = evt.pageX;
mouseY = evt.pageY;
}
this.scopeEval = function(code) {return eval(code)} //for debug purposes
//canvas = document.getElementById("canvas");
//ctx = canvas.getContext("2d");
//setInterval(render, 16);
function readBigDec(view, off, mkwii) {
if (mkwii) return view.getFloat32(off);
else return view.getInt32(off, end)/Fixed32Point;
}
function load(buffer, mkwii) {
var mkwii = mkwii;
if (mkwii == null) mkwii = false;
end = !mkwii;
mkwiiMode = mkwii;
var time = Date.now();
//loads kcl from an array buffer.
var view = new DataView(buffer);
vertexOffset = view.getUint32(0x00, end);
normalOffset = view.getUint32(0x04, end);
planeOffset = view.getUint32(0x08, end);
octreeOffset = view.getUint32(0x0C, end);
unknown1 = readBigDec(view, 0x10, mkwii);
var vec = vec3.create();
vec[0] = readBigDec(view, 0x14, mkwii);
vec[1] = readBigDec(view, 0x18, mkwii);
vec[2] = readBigDec(view, 0x1C, mkwii);
topLeftVec = vec;
xMask = view.getUint32(0x20, end);
yMask = view.getUint32(0x24, end);
zMask = view.getUint32(0x28, end);
coordShift = view.getUint32(0x2C, end);
yShift = view.getUint32(0x30, end);
zShift = view.getUint32(0x34, end);
unknown2 = readBigDec(view, 0x38, mkwii);
//read planes, there should be as many as there is 16 byte spaces between planeOffset+0x10 and octreeOffset
offset = planeOffset+0x10;
planes = [null]; //0 index is empty
var minx=0, maxx=0, minz=0, maxz=0;
while (offset < octreeOffset) {
planes.push(new Plane(view, offset));
offset += 0x10;
var vert = planes[planes.length-1].Vertex1;
if (vert[0] < minx) minx=vert[0];
if (vert[0] > maxx) maxx=vert[0];
if (vert[2] < minz) minz=vert[2];
if (vert[2] > maxz) maxz=vert[2];
}
console.log("minx: "+minx+" maxx: "+maxx+" minz: "+minz+" maxz: "+maxz)
//var sfx = canvas.width/(maxx-minx);
//var sfy = canvas.height/(maxz-minz);
//offx = -((minx+maxx)/2);
//offz = -((minz+maxz)/2);
//sf = Math.min(sfx, sfy)*0.8;
octree = []
var rootNodes = ((~xMask >> coordShift) + 1) * ((~yMask >> coordShift) + 1) * ((~zMask >> coordShift) + 1);
for (var i=0; i<rootNodes; i++) {
var off = octreeOffset+i*4;
octree.push(decodeCube(octreeOffset, off, view));
}
loaded = true;
//alert("process took "+(Date.now()-time)+"ms");
}
function render() {
if (!loaded) return;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.translate(canvas.width/2, canvas.height/2);
ctx.scale(sf, sf);
ctx.translate(offx, offz);
ctx.strokeStyle = "#000000";
ctx.lineWidth = 5;
testDrawPlanes(planes);
var test = getPlanesAt(((mouseX-canvas.width/2)/sf)-offx, topLeftVec[1]+Math.random()*(~yMask), ((mouseY-canvas.height/2)/sf)-offz);
ctx.strokeStyle = "#FF0000";
ctx.lineWidth = 20;
testDrawPlanes(test);
ctx.strokeStyle = "#000000";
ctx.lineWidth = 3;
drawOctreeBorders();
ctx.restore();
/*if (test5 != null) {
ctx.lineWidth = 0.01;
ctx.save();
ctx.translate(canvas.width/2, canvas.height/2);
ctx.scale(sf, sf);
ctx.translate(offx, offz);
ctx.scale(1024, 1024);
ctx.strokeStyle = "#0000FF";
var mdl = test5.modelBuffers[0];
for (var i=0; i<mdl.length; i++) {
var strip = mdl[i].strips[0];
var off = 0;
for (var j=0; j<strip.verts/3; j++) {
ctx.beginPath();
ctx.moveTo(strip.posArray[off], strip.posArray[off+2]);
ctx.lineTo(strip.posArray[off+3], strip.posArray[off+5]);
ctx.lineTo(strip.posArray[off+6], strip.posArray[off+8]);
ctx.closePath();
ctx.stroke();
off += 9;
}
}
ctx.restore();
}*/
}
function drawOctreeBorders() {
var size = 1<<coordShift;
for (var x=0; x<((~xMask >> coordShift) + 1); x++) {
for (var z=0; z<((~zMask >> coordShift) + 1); z++) {
ctx.strokeRect(topLeftVec[0]+size*x, topLeftVec[2]+size*z, size, size);
}
}
}
function testDrawPlanes(planes) {
for (var i=1; i<planes.length; i++) {
var plane = planes[i];
ctx.beginPath();
ctx.moveTo(plane.Vertex1[0], plane.Vertex1[2]);
ctx.lineTo(plane.Vertex2[0], plane.Vertex2[2]);
ctx.lineTo(plane.Vertex3[0], plane.Vertex3[2]);
ctx.closePath();
ctx.stroke();
}
}
function getPlanesAt(x, y, z) {
x -= topLeftVec[0];
y -= topLeftVec[1];
z -= topLeftVec[2];
if (x<0 || y<0 || z<0) return []; //no collision
else {
x = Math.floor(x);
y = Math.floor(y);
z = Math.floor(z);
if ((x&xMask)>0 || (y&yMask)>0 || (z&zMask)>0) return []; //no collision
var index = (x>>coordShift)|((y>>coordShift)<<yShift)|((z>>coordShift)<<zShift)
return traverseOctree(octree[index], x, y, z, coordShift-1);
}
}
function traverseOctree(node, x, y, z, shift) {
if (node.leaf) return node.realTris;
//otherwise we're a node! find next index and traverse
var index = ((x>>shift)&1)|(((y>>shift)&1)<<1)|(((z>>shift)&1)<<2);
return traverseOctree(node.items[index], x, y, z, shift-1);
}
function decodeCube(baseoff, off, view) {
var data = view.getUint32(off, end);
var off2 = baseoff+(data&0x7FFFFFFF);
if (off2 >= view.byteLength) {
return {
leaf: true,
tris: [],
realTris: []
}
}
if (data&0x80000000) { //is a leaf.
off2 += 2;
var tris = [];
var realTris = [];
while (true) {
var read = view.getUint16(off2, end);
if (read == 0) break; //zero terminated
tris.push(read);
realTris.push(planes[read]);
trisMapped += 1;
off2 += 2;
}
return {
leaf: true,
tris: tris,
realTris: realTris
}
} else { //contains 8 more cubes
var cubes = [];
var boff = off2;
for (var i=0; i<8; i++) {
cubes.push(decodeCube(boff, off2, view));
off2 += 4;
}
return {
leaf: false,
items: cubes
}
}
}
function Plane(view, offset) {
this.Len = readBigDec(view, offset, mkwiiMode);
this.Vertex1 = readVert(view.getUint16(offset+0x4, end), view);
this.Normal = readNormal(view.getUint16(offset+0x6, end), view);
this.NormalA = readNormal(view.getUint16(offset+0x8, end), view);
this.NormalB = readNormal(view.getUint16(offset+0xA, end), view);
this.NormalC = readNormal(view.getUint16(offset+0xC, end), view);
this.CollisionType = view.getUint16(offset+0xE, end);
var crossA = vec3.cross(vec3.create(), this.NormalA, this.Normal);
var crossB = vec3.cross(vec3.create(), this.NormalB, this.Normal);
this.Vertex2 = vec3.scaleAndAdd(vec3.create(), this.Vertex1, crossB, (this.Len/vec3.dot(crossB, this.NormalC)));
this.Vertex3 = vec3.scaleAndAdd(vec3.create(), this.Vertex1, crossA, (this.Len/vec3.dot(crossA, this.NormalC)));
}
function readVert(num, view) {
var vec = vec3.create();
var loc = vertexOffset+num*0xC;
vec[0] = readBigDec(view, loc, mkwiiMode);
vec[1] = readBigDec(view, loc+0x4, mkwiiMode);
vec[2] = readBigDec(view, loc+0x8, mkwiiMode);
return vec;
}
function readNormal(num, view) {
var mkwii = mkwiiMode;
var vec = vec3.create();
if (mkwii) {
var loc = normalOffset+num*0xC;
vec[0] = view.getFloat32(loc);
vec[1] = view.getFloat32(loc+0x4);
vec[2] = view.getFloat32(loc+0x8);
} else {
var loc = normalOffset+num*0x6;
vec[0] = view.getInt16(loc, end)/4096; //fixed point
vec[1] = view.getInt16(loc+0x2, end)/4096;
vec[2] = view.getInt16(loc+0x4, end)/4096;
}
return vec;
}
}
+41
View File
@@ -0,0 +1,41 @@
//
// lz77.js
//--------------------
// Reads and decompresses lz77 (mode 0x10 only) files. In future may be able to recompress.
// by RHY3756547
//
window.lz77 = new (function() {
this.decompress = function(buffer) {
var view = new DataView(buffer);
var compType = view.getUint8(0);
var size = view.getUint32(0, true)>>8;
var targ = new ArrayBuffer(size);
var targA = new Uint8Array(targ);
var off = 4;
var dOff = 0;
var eof = buffer.byteLength;
while (off<eof) {
var flag = view.getUint8(off++);
for (var j=7; j>=0; j--) {
if (off>=eof) break;
if ((flag>>j)&1) { //1=compressed, 2=raw byte
var dat = view.getUint16(off);
off += 2;
var cOff = (dOff-(dat&4095))-1;
var len = (dat>>12)+3;
for (var k=0; k<len; k++) {
targA[dOff++] = targA[cOff++];
}
} else {
targA[dOff++] = view.getUint8(off++);
}
}
}
return targ;
}
})();
+196
View File
@@ -0,0 +1,196 @@
//
// narc.js
//--------------------
// Reads narc archives and provides access to files by directory structure.
// by RHY3756547
//
window.narc = function(input) {
this.load = load;
this.getFile = getFile;
var arc = this;
var handlers = [];
window.onmousemove = function(evt) {
mouseX = evt.pageX;
mouseY = evt.pageY;
}
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(buffer) {
arc.buffer = buffer; //we will use this data in the future.
var view = new DataView(buffer);
arc.stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
if (arc.stamp != "NARC") throw "File provided is not a NARC archive! Expected NARC, found "+arc.stamp+".";
arc.byteOrder = view.getUint16(0x4, true); //todo: check byte order and flip to little endian when necessary
arc.version = view.getUint16(0x6, true);
arc.size = view.getUint32(0x8, true);
arc.headSize = view.getUint16(0xC, true);
arc.numBlocks = view.getUint16(0xE, true);
var off = arc.headSize;
arc.sections = {};
for (var i=0; i<arc.numBlocks; i++) {
var section = readSection(view, off);
arc.sections[section.type] = section;
off = 4*Math.ceil(section.nextOff/4);
}
}
function readSection(view, off) {
var obj = {};
obj.type = readChar(view, off+0x0)+readChar(view, off+0x1)+readChar(view, off+0x2)+readChar(view, off+0x3);
obj.size = view.getUint32(off+0x4, true);
if (handlers[obj.type] == null) throw "Unknown NARC section "+obj.type+"!";
handlers[obj.type](view, off+0x8, obj);
obj.nextOff = off+obj.size;
return obj;
}
function getFile(name) {
var path = name.split("/");
var start = (path[0] == "")?1:0; //fix dirs relative to root (eg "/hi/test.bin")
var table = arc.sections["BTNF"].directories;
var curDir = table[0].entries; //root
for (var i=start; i<path.length; i++) {
var found = false;
for (var j=0; j<curDir.length; j++) {
if (curDir[j].name == path[i]) {
if (curDir[j].dir) {
found = true;
curDir = table[curDir[j].id-0xF000].entries;
break;
} else {
return readFileWithID(curDir[j].id);
}
}
}
if (!found) {
console.error("File not found: "+name+", could not find "+path[i]);
return null;
}
}
console.error("Path is not a file: "+name);
return null; //incomplete path; we ended on a directory, not a file!
}
function readFileWithID(id) {
var table = arc.sections["BTAF"].files;
var file = table[id];
var off = arc.sections["GMIF"].baseOff;
if (file == null) {
console.error("File ID invalid: "+id);
return null;
}
return arc.buffer.slice(file.start+off, file.end+off);
}
var handlers = {};
handlers["BTAF"] = function(view, off, obj) {
obj.numFiles = view.getUint16(off, true);
obj.reserved = view.getUint16(off+0x2, true);
obj.files = [];
off += 4;
for (var i=0; i<obj.numFiles; i++) {
var fl = {}
fl.start = view.getUint32(off, true);
fl.end = view.getUint32(off+4, true);
obj.files.push(fl);
off += 8;
}
}
handlers["BTNF"] = function(view, off, obj) { //filename table - includes directories and filenames.
var soff = off;
obj.directories = [];
//read root dir, then we know number of directories to read.
var root = {};
var dirOff = soff+view.getUint32(off, true);
root.firstFile = view.getUint16(off+4, true);
populateDir(view, dirOff, root);
root.numDir = view.getUint16(off+6, true);
off += 8;
obj.directories.push(root);
var n = root.numDir-1;
for (var i=0; i<n; i++) {
var dir = {};
var dirOff = soff+view.getUint32(off, true);
dir.firstFile = view.getUint16(off+4, true);
populateDir(view, dirOff, dir);
dir.parent = view.getUint16(off+6, true);
off += 8;
obj.directories.push(dir);
}
}
handlers["GMIF"] = function(view, off, obj) {
obj.baseOff = off;
}
function populateDir(view, off, dir) {
curFile = dir.firstFile;
dir.entries = [];
while (true) {
var flag = view.getUint8(off++);
var len = flag&127;
if (! (flag&128)) { //file or end of dir
if (len == 0) return;
else {
dir.entries.push({
dir: false,
id: curFile++,
name: readString(view, off, len)
})
off += len;
}
} else {
var dirID = view.getUint16(off+len, true);
dir.entries.push({
dir: true,
id: dirID,
name: readString(view, off, len)
});
off += len+2;
}
}
}
function readString(view, off, length) {
var str = "";
for (var i=0; i<length; i++) {
str += readChar(view, off++);
}
return str;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
if (input != null) {
if (typeof input == "string") {
var xml = new XMLHttpRequest();
xml.responseType = "arraybuffer";
xml.open("GET", input, true);
xml.onload = function() {
load(xml.response);
}
xml.send();
} else {
load(input);
}
}
}
+170
View File
@@ -0,0 +1,170 @@
//
// ndsFS.js
//--------------------
// Reads nds roms using nitroFS and provides access to files by directory structure.
// by RHY3756547
//
window.ndsFS = function(input) {
this.load = load;
this.getFile = getFile;
var arc = this;
var handlers = [];
window.onmousemove = function(evt) {
mouseX = evt.pageX;
mouseY = evt.pageY;
}
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(buffer) {
arc.buffer = buffer; //we will use this data in the future.
var view = new DataView(buffer);
arc.view = view;
arc.sections = {};
arc.nameOff = view.getUint32(0x40, true);
arc.fileOff = view.getUint32(0x48, true)
arc.sections["BTNF"] = {};
handlers["BTNF"](view, arc.nameOff, arc.sections["BTNF"]) //file name table
/*arc.sections["BTAF"] = {};
handlers["BTAF"](view, , arc.sections["BTAF"]) //file alloc table */
}
function getFile(name) {
var path = name.split("/");
var start = (path[0] == "")?1:0; //fix dirs relative to root (eg "/hi/test.bin")
var table = arc.sections["BTNF"].directories;
var curDir = table[0].entries; //root
for (var i=start; i<path.length; i++) {
var found = false;
for (var j=0; j<curDir.length; j++) {
if (curDir[j].name == path[i]) {
if (curDir[j].dir) {
found = true;
curDir = table[curDir[j].id-0xF000].entries;
break;
} else {
return readFileWithID(curDir[j].id);
}
}
}
if (!found) {
console.error("File not found: "+name+", could not find "+path[i]);
return null;
}
}
console.error("Path is not a file: "+name);
return null; //incomplete path; we ended on a directory, not a file!
}
function readFileWithID(id) {
var off = arc.fileOff+id*8;
/*var table = arc.sections["BTAF"].files;
var file = table[id];
if (file == null) {
console.error("File ID invalid: "+id);
return null;
}*/
return arc.buffer.slice(arc.view.getUint32(off, true), arc.view.getUint32(off+4, true));
}
var handlers = {};
handlers["BTAF"] = function(view, off, obj) {
obj.files = [];
for (var i=0; i<obj.numFiles; i++) {
var fl = {}
fl.start = view.getUint32(off, true);
fl.end = view.getUint32(off+4, true);
obj.files.push(fl);
off += 8;
}
}
handlers["BTNF"] = function(view, off, obj) { //filename table - includes directories and filenames.
var soff = off;
obj.directories = [];
//read root dir, then we know number of directories to read.
var root = {};
var dirOff = soff+view.getUint32(off, true);
root.firstFile = view.getUint16(off+4, true);
populateDir(view, dirOff, root);
root.numDir = view.getUint16(off+6, true);
off += 8;
obj.directories.push(root);
var n = root.numDir-1;
for (var i=0; i<n; i++) {
var dir = {};
var dirOff = soff+view.getUint32(off, true);
dir.firstFile = view.getUint16(off+4, true);
populateDir(view, dirOff, dir);
dir.parent = view.getUint16(off+6, true);
off += 8;
obj.directories.push(dir);
}
}
function populateDir(view, off, dir) {
curFile = dir.firstFile;
dir.entries = [];
while (true) {
var flag = view.getUint8(off++);
var len = flag&127;
if (! (flag&128)) { //file or end of dir
if (len == 0) return;
else {
dir.entries.push({
dir: false,
id: curFile++,
name: readString(view, off, len)
})
off += len;
}
} else {
var dirID = view.getUint16(off+len, true);
dir.entries.push({
dir: true,
id: dirID,
name: readString(view, off, len)
});
off += len+2;
}
}
}
function readString(view, off, length) {
var str = "";
for (var i=0; i<length; i++) {
str += readChar(view, off++);
}
return str;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
if (input != null) {
if (typeof input == "string") {
var xml = new XMLHttpRequest();
xml.responseType = "arraybuffer";
xml.open("GET", input, true);
xml.onload = function() {
load(xml.response);
}
xml.send();
} else {
load(input);
}
}
}
+113
View File
@@ -0,0 +1,113 @@
//
// netKart.js
//--------------------
// Singleton for serializing and restoring kart data.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /entities/kart.js
//
window.netKart = new function() {
var animNames = ["drive", "win", "lose", "spin"]
this.saveKart = saveKart;
this.restoreKart = restoreKart;
function saveKart(view, off, k, input) { // requires 0x60 bytes of space from the offset location
saveVec3(view, off, k.pos);
saveVec3(view, off+0xC, k.vel);
view.setFloat32(off+0x18, k.angle, true);
view.setFloat32(off+0x1C, k.physicalDir, true);
view.setFloat32(off+0x20, k.driftOff, true);
if (k.cannon != null) view.setUint16(off+0x24, k.cannon, true);
else view.setUint16(off+0x24, 0xFFFF, true);
view.setUint16(off+0x26, k.airTime, true);
view.setUint16(off+0x28, k.lastCollided, true);
view.setUint8(off+0x2A, k.boostMT);
view.setUint8(off+0x2B, k.boostNorm);
view.setUint16(off+0x2C, k.stuckTo, true);
view.setUint8(off+0x2E, k.wheelTurn);
saveVec3(view, off+0x30, k.kartColVel);
view.setUint8(off+0x3C, k.kartColTimer);
saveVec3(view, off+0x3D, k.kartTargetNormal);
saveVec3(view, off+0x49, k.trackAttach);
var driftFlags = ((k.drifting)?1:0)|(k.driftMode<<1)|((k.driftLanded)?8:0);
view.setUint8(off+0x55, driftFlags);
view.setUint8(off+0x56, animNames.indexOf(k.animMode));
var binput = ((input.accel)?1:0)|((input.decel)?2:0)|((input.drift)?4:0);
view.setUint8(off+0x57, binput);
view.setFloat32(off+0x58, input.turn, true);
view.setFloat32(off+0x5C, input.airTurn, true);
}
function restoreKart(view, off, k) { // we use the same endianness format as the ds to avoid confusion.
try {
k.pos = readVec3(view, off, k.pos);
k.vel = readVec3(view, off+0xC, k.vel);
k.angle = view.getFloat32(off+0x18, true);
k.physicalDir = view.getFloat32(off+0x1C, true);
k.driftOff = view.getFloat32(off+0x20, true);
k.cannon = view.getUint16(off+0x24, true);
if (k.cannon == 0xFFFF) k.cannon = null;
k.airTime = view.getUint16(off+0x26, true);
k.lastCollided = view.getUint16(off+0x28, true);
k.boostMT = view.getUint8(off+0x2A);
k.boostNorm = view.getUint8(off+0x2B);
k.stuckTo = view.getUint16(off+0x2C, true);
k.wheelTurn = view.getUint8(off+0x2E);
k.kartColVel = readVec3(view, off+0x30, k.kartColVel);
k.kartColTimer = view.getUint8(off+0x3C);
k.kartTargetNormal = readVec3(view, off+0x3D, k.kartTargetNormal);
k.trackAttach = readVec3(view, off+0x49, k.trackAttach);
var driftFlags = view.getUint8(off+0x55);
k.drifting = driftFlags&1;
k.driftMode = (driftFlags>>1)&3;
k.driftLanded = driftFlags&8;
k.animMode = animNames[view.getUint8(off+0x56)];
k.controller.binput = view.getUint8(off+0x57);
k.controller.turn = view.getFloat32(off+0x58, true);
k.controller.airTurn = view.getFloat32(off+0x5C, true);
} catch (err) {
console.err("Kart restore failure:"+err.message);
//failed to restore kart data. may wish to disconnect on this, but it's probably better to not react.
}
}
function saveVec3(view, off, vec) {
var vec = vec;
if (vec == null) vec = [NaN, NaN, NaN];
view.setFloat32(off, vec[0], true);
view.setFloat32(off+4, vec[1], true);
view.setFloat32(off+8, vec[2], true);
}
function readVec3(view, off, vec) {
var first = view.getFloat32(off, true);
if (isNaN(first)) return null;
vec = vec3.create();
vec[0] = first;
vec[1] = view.getFloat32(off+4, true);
vec[2] = view.getFloat32(off+8, true);
return vec;
}
}
+37
View File
@@ -0,0 +1,37 @@
//
// nftr.js
//--------------------
// Reads NFTR fonts and compiles them to a texture and character lookup table. Texture is replaceable.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js
//
window.nftr = function(input) {
var mainOff;
var mainObj = this;
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var header = null;
var offset = 0;
var tex;
//nitro 3d header
header = nitro.readHeader(view);
//debugger;
if (header.stamp != "RTFN") throw "NFTR invalid. Expected RTFN, found "+header.stamp;
offset = header.sectionOffsets[0];
//end nitro
mainOff = offset;
}
}
+85
View File
@@ -0,0 +1,85 @@
//
// nitro.js
//--------------------
// General purpose functions for nitro formats, eg. NSBTX or NSBMD
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
//
window.nitro = new function() {
this.readHeader = function(view) { //input: DataView with base offset at header position
var stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
var unknown1 = view.getUint32(0x4, true);
var filesize = view.getUint32(0x8, true);
var headsize = view.getUint16(0xC, true);
var numSections = view.getUint16(0xE, true);
var sectionOffsets = [];
for (var i=0; i<numSections; i++) {
sectionOffsets.push(view.getUint32(0x10+i*4, true));
}
return {
stamp: stamp,
unknown1: unknown1,
filesize: filesize,
headsize: headsize,
numSections: numSections,
sectionOffsets: sectionOffsets
}
}
this.read3dInfo = function(view, offset, dataHandler) {
var baseOff = offset;
offset += 1; //skip dummy
var numObjects = view.getUint8(offset++);
var secSize = view.getUint16(offset, true);
offset += 2;
//unknown block. documentation out of 10
var uhdSize = view.getUint16(offset, true);
offset += 2;
var usecSize = view.getUint16(offset, true);
offset += 2;
var unknown = view.getUint32(offset, true); //usually 0x0000017F
offset += 4;
var objectUnk = []
for (var i=0; i<numObjects; i++) {
var uk1 = view.getUint16(offset, true);
var uk2 = view.getUint16(offset+2, true);
objectUnk.push({uk1: uk1, uk2: uk2});
offset += 4;
}
//info block
var ihdSize = view.getUint16(offset, true);
offset += 2;
var isecSize = view.getUint16(offset, true);
offset += 2;
var objectData = []
for (var i=0; i<numObjects; i++) {
var data = dataHandler(view, offset, baseOff, i); //must return object with "nextoff" as offset after reading data
objectData.push(data)
offset = data.nextoff;
}
var names = []
for (var i=0; i<numObjects; i++) {
var name = "";
for (var j=0; j<16; j++) {
name += readChar(view, offset++)
}
names.push(name);
}
return {
numObjects: numObjects,
unknown: unknown,
objectUnk: objectUnk,
objectData: objectData,
names: names,
nextoff: offset
}
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+393
View File
@@ -0,0 +1,393 @@
//
// nkm.js
//--------------------
// Loads nkm files and provides a variety of functions for accessing and using the data.
//
// nkm files usually drive the game logic of tracks (checkpoints, ai waypoints, objects on track) so it's pretty
// crucial to have this up and running.
//
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
//
window.nkm = function(input, mkwii) {
//todo, support versions for other games (MKWii etc)
this.load = load;
var thisObj = this;
var handlers = [];
window.onmousemove = function(evt) {
mouseX = evt.pageX;
mouseY = evt.pageY;
}
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(buffer, mkwii) {
var view = new DataView(buffer);
thisObj.stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
thisObj.version = view.getUint16(0x4, true);
var n = view.getUint16(0x6, true);
var off = 8;
var sections = {};
for (var i=0; i<(n-8)/4; i++) {
var soff = view.getUint32(off, true);
var section = readSection(view, soff+n)
sections[section.type] = section;
off += 4;
}
thisObj.sections = sections;
}
function readSection(view, off) {
var obj = {};
obj.type = readChar(view, off+0x0)+readChar(view, off+0x1)+readChar(view, off+0x2)+readChar(view, off+0x3);
if (obj.type == "STAG") {
handlers["STAG"](view, off, obj);
} else {
var handler = handlers[obj.type];
if (handler == null) {
console.error("Unknown NKM section type "+obj.type+"!");
return obj;
}
var entN = view.getUint32(off+0x4, true);
off += 0x8;
var entries = [];
for (var i=0; i<entN; i++) {
var item = handler(view, off);
entries.push(item);
off = item.nextOff;
}
obj.entries = entries;
}
return obj;
}
handlers["OBJI"] = function(view, off) {
var obj = {};
obj.pos = vec3.create();
obj.pos[0] = view.getInt32(off, true)/4096;
obj.pos[1] = view.getInt32(off+4, true)/4096;
obj.pos[2] = view.getInt32(off+8, true)/4096;
obj.angle = [];
obj.angle[0] = view.getInt32(off+0xC, true)/4096;
obj.angle[1] = view.getInt32(off+0x10, true)/4096;
obj.angle[2] = view.getInt32(off+0x14, true)/4096;
obj.scale = vec3.create();
obj.scale[0] = view.getInt32(off+0x18, true)/4096;
obj.scale[1] = view.getInt32(off+0x1C, true)/4096;
obj.scale[2] = view.getInt32(off+0x20, true)/4096;
obj.ID = view.getUint16(off+0x24, true);
obj.routeID = view.getUint16(off+0x26, true);
obj.setting1 = view.getUint32(off+0x28, true);
obj.setting2 = view.getUint32(off+0x2C, true);
obj.setting3 = view.getUint32(off+0x30, true);
obj.setting4 = view.getUint32(off+0x34, true);
obj.timeTrials = view.getUint32(off+0x38, true);
obj.nextOff = off+0x3C;
return obj;
}
handlers["PATH"] = function(view, off) {
var obj = {};
obj.routeID = view.getUint8(off);
obj.loop = view.getUint8(off+1);
obj.numPts = view.getUint16(off+2, true);
obj.nextOff = off+0x4;
return obj;
}
handlers["POIT"] = function(view, off) {
var obj = {};
obj.pos = vec3.create();
obj.pos[0] = view.getInt32(off, true)/4096;
obj.pos[1] = view.getInt32(off+4, true)/4096;
obj.pos[2] = view.getInt32(off+8, true)/4096;
obj.pointInd = view.getUint16(off+0xC, true);
obj.duration = view.getUint16(off+0xE, true);
obj.unknown = view.getUint32(off+0x10, true);
obj.nextOff = off+0x14;
return obj;
}
handlers["STAG"] = function(view, off, obj) {
obj.courseID = view.getUint16(off+0x4, true);
obj.laps = view.getUint16(off+0x6, true); //doubles as battle mode duration
obj.unknown = view.getUint8(off+0x8);
obj.fogEnable = view.getUint8(off+0x9);
obj.fogMode = view.getUint8(off+0xA);
obj.fogSlope = view.getUint8(off+0xB);
//skip 8 bytes of unknown, probably more fog stuff. (disabled for now)
obj.fogDist = view.getInt32(off+0x14, true)/4096;
obj.fogCol = readRGB(view, off+0x18);
obj.fogAlpha = view.getUint16(off+0x1A, true);
obj.kclCol = [readRGB(view, off+0x1C), readRGB(view, off+0x1E), readRGB(view, off+0x20), readRGB(view, off+0x22)];
//unknown 8 bytes again
return obj;
}
handlers["KTPS"] = function(view, off) { //start positions
var obj = {};
obj.pos = vec3.create();
obj.pos[0] = view.getInt32(off, true)/4096;
obj.pos[1] = view.getInt32(off+4, true)/4096;
obj.pos[2] = view.getInt32(off+8, true)/4096;
obj.angle = [];
obj.angle[0] = view.getInt32(off+0xC, true)/4096;
obj.angle[1] = view.getInt32(off+0x10, true)/4096;
obj.angle[2] = view.getInt32(off+0x14, true)/4096;
obj.id1 = view.getInt16(off+0x18, true);
obj.id2 = view.getInt16(off+0x1A, true);
obj.nextOff = off+0x1C;
return obj;
}
handlers["KTP2"] = handlers["KTPS"]; //must pass this point for lap to count. ids irrelevant
handlers["KTPJ"] = function(view, off){
var obj = handlers["KTPS"](view, off);
obj.respawnID = view.getInt32(off+0x1C, true);
obj.nextOff += 0x4;
return obj;
}; //respawn points. id1 is cpu route, id2 is item route
handlers["KTPC"] = handlers["KTPS"]; //cannon positions. id1 is cpu route, id2 is cannon id
handlers["KTPM"] = handlers["KTPS"]; //mission kart position. must pass for mission to succeed.
handlers["CPOI"] = function(view, off) {
var obj = {};
obj.x1 = view.getInt32(off, true)/4096;
obj.z1 = view.getInt32(off+0x4, true)/4096;
obj.x2 = view.getInt32(off+0x8, true)/4096;
obj.z2 = view.getInt32(off+0xC, true)/4096;
obj.sinus = view.getInt32(off+0x10, true)/4096;
obj.cosinus = view.getInt32(off+0x14, true)/4096;
obj.distance = view.getInt32(off+0x18, true)/4096;
obj.nextSection = view.getInt16(off+0x1C, true);
obj.currentSection = view.getInt16(off+0x1E, true);
obj.keyPoint = view.getInt16(off+0x20, true);
obj.respawn = view.getUint8(off+0x22);
obj.unknown = view.getUint8(off+0x23);
obj.nextOff = off+0x24;
return obj;
}
handlers["CPAT"] = function(view, off) { //checkpoint path
var obj = {};
obj.startInd = view.getInt16(off, true);
obj.pathLen = view.getInt16(off+0x2, true);
obj.dest = [view.getInt8(off+0x4)];
var tmp = view.getInt8(off+0x5)
if (tmp != -1) obj.dest.push(tmp);
var tmp2 = view.getInt8(off+0x6)
if (tmp2 != -1) obj.dest.push(tmp2);
obj.source = [view.getInt8(off+0x7)];
var tmp3 = view.getInt8(off+0x8)
if (tmp3 != -1) obj.source.push(tmp3);
var tmp4 = view.getInt8(off+0x9)
if (tmp4 != -1) obj.source.push(tmp4);
obj.sectionOrder = view.getInt16(off+0xA, true);
obj.nextOff = off+0xC;
return obj;
}
handlers["MEPA"] = function(view, off) { //checkpoint path
var obj = {};
obj.startInd = view.getInt16(off, true);
obj.pathLen = view.getInt16(off+0x2, true);
obj.dest = [];
var o = off+4;
for(var i=0; i<8; i++) {
var tmp = view.getInt8(o++);
if (tmp != -1) obj.dest.push(tmp);
}
obj.source = [];
for(var i=0; i<8; i++) {
var tmp = view.getInt8(o++);
if (tmp != -1) obj.source.push(tmp);
}
obj.nextOff = o;
return obj;
}
handlers["IPAT"] = handlers["CPAT"]; //item path
handlers["EPAT"] = handlers["CPAT"]; //enemy path
handlers["IPOI"] = function(view, off) {
var obj = {};
obj.pos = vec3.create();
obj.pos[0] = view.getInt32(off, true)/4096;
obj.pos[1] = view.getInt32(off+4, true)/4096;
obj.pos[2] = view.getInt32(off+8, true)/4096;
obj.unknown1 = view.getUint8(off+0xC); //tends to be 0 or FF
obj.unknown2 = view.getUint8(off+0xD);
obj.unknown3 = view.getUint8(off+0xE);
obj.unknown4 = view.getUint8(off+0xF);
obj.unknown5 = view.getUint32(off+0x10, true); //tends to be 0
obj.nextOff = off+0x14;
return obj;
}
handlers["EPOI"] = function(view, off) {
var obj = {};
obj.pos = vec3.create();
obj.pos[0] = view.getInt32(off, true)/4096;
obj.pos[1] = view.getInt32(off+4, true)/4096;
obj.pos[2] = view.getInt32(off+8, true)/4096;
obj.pointSize = view.getInt32(off+0xC, true)/4096;
obj.cpuDrift = view.getUint16(off+0x10, true); //will find out what this means in due time, a watcher on this value while a cpu is going around the track should clear things up.
obj.unknown1 = view.getUint16(off+0x12, true); //tends to be 0
obj.unknown2 = view.getUint32(off+0x14, true); //tends to be 0
obj.nextOff = off+0x18;
return obj;
}
handlers["MEPO"] = function(view, off) { //theres usually 5 of these LOL!!! im not sorry
var obj = {};
obj.pos = vec3.create();
obj.pos[0] = view.getInt32(off, true)/4096;
obj.pos[1] = view.getInt32(off+4, true)/4096;
obj.pos[2] = view.getInt32(off+8, true)/4096;
obj.pointSize = view.getInt32(off+0xC, true)/4096;
obj.cpuDrift = view.getUint16(off+0x10, true); //will find out what this means in due time, a watcher on this value while a cpu is going around the track should clear things up.
obj.unknown1 = view.getUint16(off+0x12, true); //tends to be 0
obj.unknown1 = view.getUint32(off+0x14, true); //tends to be 0
obj.nextOff = off+0x18;
return obj;
}
handlers["AREA"] = function(view, off) { //area for cameras. this section is ridiculous - will need thorough investigation if we want to get race spectate cameras working.
var obj = {};
obj.pos = vec3.create();
obj.pos[0] = view.getInt32(off, true)/4096;
obj.pos[1] = view.getInt32(off+4, true)/4096;
obj.pos[2] = view.getInt32(off+8, true)/4096;
obj.dimensions = vec3.create();
obj.dimensions[0] = view.getInt32(off+0xC, true)/4096;
obj.dimensions[1] = view.getInt32(off+0x10, true)/4096;
obj.dimensions[2] = view.getInt32(off+0x14, true)/4096;
//44 bytes of unknown, ouch!
obj.came = view.getUint8(off+0x43);
obj.one = view.getUint32(off+0x44, true); //good ole one
obj.nextOff = off+0x48;
return obj;
}
handlers["CAME"] = function(view, off) { //cameras. not really much known about these right now.
var obj = {};
obj.pos1 = vec3.create();
obj.pos1[0] = view.getInt32(off, true)/4096;
obj.pos1[1] = view.getInt32(off+4, true)/4096;
obj.pos1[2] = view.getInt32(off+8, true)/4096;
obj.angle = vec3.create();
obj.angle[0] = view.getInt32(off+0xC, true)/4096;
obj.angle[1] = view.getInt32(off+0x10, true)/4096;
obj.angle[2] = view.getInt32(off+0x14, true)/4096;
obj.pos2 = vec3.create();
obj.pos2[0] = view.getInt32(off+0x18, true)/4096;
obj.pos2[1] = view.getInt32(off+0x1C, true)/4096;
obj.pos2[2] = view.getInt32(off+0x20, true)/4096;
obj.pos3 = vec3.create();
obj.pos3[0] = view.getInt32(off+0x24, true)/4096;
obj.pos3[1] = view.getInt32(off+0x28, true)/4096;
obj.pos3[2] = view.getInt32(off+0x2C, true)/4096;
//44 bytes of unknown, ouch!
obj.zoomSpeedM1 = view.getInt16(off+0x30, true)/4096;
obj.zoomStart = view.getInt16(off+0x32, true)/4096; //alters zoom somehow
obj.zoomEnd = view.getInt16(off+0x34, true)/4096;
obj.zoomSpeedM2 = view.getInt16(off+0x36, true)/4096;
obj.zoomMark1 = view.getInt16(off+0x38, true)/4096; //zoom speed changes at zoom marks
obj.zoomMark2 = view.getInt16(off+0x3A, true)/4096;
obj.zoomSpeed = view.getInt16(off+0x3C, true)/4096;
obj.camType = view.getInt16(off+0x3E, true);
obj.camRoute = view.getInt16(off+0x40, true);
obj.routeSpeed = view.getInt16(off+0x42, true);
obj.pointSpeed = view.getInt16(off+0x44, true);
obj.duration = view.getInt16(off+0x46, true);
obj.nextCam = view.getInt16(off+0x48, true);
obj.firstCam = view.getUint8(off+0x4A);
obj.one = view.getUint8(off+0x4B); //tends to be 1 if cam type is 5
obj.nextOff = off+0x4C;
return obj;
}
function readRGB(view, offset) {
var dat = view.getUint16(offset, true);
var col = [dat&31, (dat>>5)&31, (dat>>10)&31];
return col;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
if (input != null) {
if (typeof input == "string") {
var xml = new XMLHttpRequest();
xml.responseType = "arraybuffer";
xml.open("GET", input, true);
xml.onload = function() {
load(xml.response);
}
xml.send();
} else {
load(input, mkwii);
}
}
}
+246
View File
@@ -0,0 +1,246 @@
//
// nsbca.js
//--------------------
// Reads NSBCA files (bone animations) for use in combination with an NSBMD (model) file
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js
//
// most investigation done by florian for the mkds course modifier.
// I've tried to keep things much simpler than they were in his code.
window.nsbca = function(input) {
var mainOff;
var animData;
var speeds = [1.0, 0.5, 1/3];
var mainObj = this;
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var header = null;
var offset = 0;
var tex;
//nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "BCA0") throw "NSBCA invalid. Expected BCA0, found "+header.stamp;
if (header.numSections > 1) throw "NSBCA invalid. Too many sections - should have 1 maximum.";
offset = header.sectionOffsets[0];
//end nitro
mainOff = offset;
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (stamp != "JNT0") throw "NSBCA invalid. Expected JNT0, found "+stamp;
animData = nitro.read3dInfo(view, mainOff+8, animInfoHandler);
mainObj.animData = animData;
}
function animInfoHandler(view, off, base) {
var offset = mainOff + view.getUint32(off, true);
var obj = {nextoff: off + 4}
readAnim(view, offset, obj);
return obj;
}
function readAnim(view, off, obj) {
obj.baseOff = off;
obj.stamp = readChar(view, off+0x0)+readChar(view, off+0x2)+readChar(view, off+0x3);
obj.frames = view.getUint16(off+0x4, true);
obj.numObj = view.getUint16(off+0x6, true);
obj.unknown = view.getUint32(off+0x8, true); //NOTE: this may be a flag. used later to specify extra frames if not = 3
obj.off1 = view.getUint32(off+0xC, true);
obj.off2 = view.getUint32(off+0x10, true); //offset to rotation data
off += 0x14;
var transforms = [];
for (var i=0; i<obj.numObj; i++) {
var off2 = view.getUint16(off, true)+obj.baseOff;
transforms.push(readTrans(view, off2, obj));
off += 2;
}
obj.trans = transforms;
}
function readTrans(view, off, obj) {
var flag = view.getUint16(off, true); //--zyx-Sr-RZYX-T-
off += 4;
var transform = {};
if (!((flag>>1) & 1)) { //T: translation
var translate = [[], [], []]; //store translations in x,y,z arrays
var tlExtra = [];
for (var i=0; i<3; i++) { //iterate over x y z (for translation)
var f = (flag>>(3+i))&1;
if (f) { //one value
translate[i].push(view.getInt32(off, true)/4096);
off += 4;
} else { //credit to florian for cracking this.
var inf = {};
inf.startFrame = view.getUint16(off, true)
var dat = view.getUint16(off+2, true)
inf.endFrame = dat&0x0FFF;
inf.halfSize = ((dat>>12)&3);
inf.speed = speeds[((dat>>14)&3)];
inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame);
var length = Math.floor((obj.frames+extra)*inf.speed);
var w = (inf.halfSize)?2:4;
var off2 = obj.baseOff+inf.off;
for (var j=0; j<length; j++) {
translate[i].push(((inf.halfSize)?view.getInt16(off2, true):view.getInt32(off2, true))/4096);
off2 += w;
}
tlExtra[i] = inf;
off += 8;
}
}
transform.translate = translate;
transform.tlExtra = tlExtra;
}
if (!((flag>>6) & 1)) { //R: rotation, which is both fun and exciting.
var rotate = [];
var rotExtra;
var f = (flag>>8)&1;
if (f) { //one value
rotate.push(readRotation(view, off, obj));
off += 4;
} else { //credit to florian for cracking this.
var inf = {};
inf.startFrame = view.getUint16(off, true)
var dat = view.getUint16(off+2, true) //low 12 bits are end frame, high 4 are size flag and speed
inf.endFrame = dat&0x0FFF;
inf.halfSize = ((dat>>12)&3); //not used by rotation?
inf.speed = speeds[((dat>>14)&3)];
inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame);
//florian's rotate code seems to ignore this extra value. I'll need more examples of nsbca to test this more thoroughly.
var length = Math.floor((obj.frames+extra)*inf.speed);
var off2 = obj.baseOff+inf.off;
try {
for (var j=0; j<length; j++) {
rotate.push(readRotation(view, off2, obj));
off2 += 2;
}
} catch (e) {
}
rotExtra = inf;
off += 8;
}
transform.rotate = rotate;
transform.rotExtra = rotExtra;
}
if (!((flag>>9) & 1)) { //S: scale
var scale = [[], [], []]; //store scales in x,y,z arrays
var scExtra = [];
for (var i=0; i<3; i++) { //iterate over x y z (for scale)
var f = (flag>>(11+i))&1;
if (f) { //one value
scale[i].push({
s1: view.getInt32(off, true)/4096,
s2: view.getInt32(off, true)/4096
});
off += 8;
} else { //credit to florian for cracking this.
var inf = {};
inf.startFrame = view.getUint16(off, true)
var dat = view.getUint16(off+2, true)
inf.endFrame = dat&0x0FFF;
inf.halfSize = ((dat>>12)&3);
inf.speed = speeds[((dat>>14)&3)];
inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame);
var length = Math.ceil((obj.frames+extra)*inf.speed);
var w = ((inf.halfSize)?2:4);
var off2 = obj.baseOff+inf.off;
for (var j=0; j<length; j++) {
scale[i].push({
s1: ((inf.halfSize)?view.getInt16(off2, true):view.getInt32(off2, true))/4096,
s2: ((inf.halfSize)?view.getInt16(off2+w, true):view.getInt32(off2+w, true))/4096
});
off2 += w*2;
}
scExtra[i] = inf;
off += 8;
}
}
transform.scale = scale;
transform.scExtra = scExtra;
}
return transform;
}
function readRotation(view, off, obj) {
var dat = view.getInt16(off, true);
var ind = (dat&0x7FFF);
var mode = (dat>>15);
if (mode) { //rotation is pivot
var off2 = obj.baseOff+obj.off1+ind*6; //jump to rotation data
return {
pivot: true,
param: view.getUint16(off2, true),
a: view.getInt16(off2+2, true)/4096,
b: view.getInt16(off2+4, true)/4096
};
} else {
var off2 = obj.baseOff+obj.off2+ind*10; //jump to rotation data
var d1 = view.getInt16(off2, true);
var d2 = view.getInt16(off2+2, true);
var d3 = view.getInt16(off2+4, true);
var d4 = view.getInt16(off2+6, true);
var d5 = view.getInt16(off2+8, true);
var i6 = ((d5&7)<<12) | ((d1&7)<<9) | ((d2&7)<<6) | ((d3&7)<<3) | ((d4&7));
if (i6&4096) i6 = (-8192)+i6;
var v1 = [d1>>3, d2>>3, d3>>3]
var v2 = [d4>>3, d5>>3, i6]
vec3.scale(v1, v1, 1/4096);
vec3.scale(v2, v2, 1/4096);
var v3 = vec3.cross([], v1, v2)
var mat = [
v1[0], v1[1], v1[2],
v2[0], v2[1], v2[2],
v3[0], v3[1], v3[2]
]
return {
pivot: false,
mat: mat
};
}
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+411
View File
@@ -0,0 +1,411 @@
//
// nsbmd.js
//--------------------
// Reads NSBMD models and any texture data within them.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js
// /formats/nsbtx.js
//
window.nsbmd = function(input) {
var mainOff, modelData, texPalOff, materials;
var mainObj = this;
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
mainObj.hasBillboards = false;
var view = new DataView(input);
var header = null;
var offset = 0;
var tex;
//nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "BMD0") throw "NSBMD invalid. Expected BMD0, found "+header.stamp;
if (header.numSections > 2) throw "NSBMD invalid. Too many sections - should have 2 maximum.";
if (header.numSections == 2) tex = new nsbtx(input.slice(header.sectionOffsets[1]), true, true);
offset = header.sectionOffsets[0];
//end nitro
mainOff = offset;
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (stamp != "MDL0") throw "NSBMD invalid. Expected MDL0, found "+stamp;
mainObj.tex = tex;
modelData = nitro.read3dInfo(view, mainOff+8, modelInfoHandler);
mainObj.modelData = modelData;
}
function modelInfoHandler(view, offset) {
var mdlOff = view.getUint32(offset, true);
var off = mainOff+mdlOff;
var obj = readModelData(view, off);
obj.nextoff = offset+4;
return obj;
}
function readModelData(view, offset) {
var head = {}
head.blockSize = view.getUint32(offset, true);
head.bonesOffset = offset+view.getUint32(offset+4, true);
head.materialsOffset = offset+view.getUint32(offset+8, true);
head.polyStartOffset = offset+view.getUint32(offset+0xC, true);
head.polyEndOffset = offset+view.getUint32(offset+0x10, true);
head.numObjects = view.getUint8(offset+0x17);
head.numMaterials = view.getUint8(offset+0x18);
head.numPolys = view.getUint8(offset+0x19);
head.maxStack = view.getUint8(offset+0x1A);
head.scale = view.getInt32(offset+0x1C, true)/4096;
head.numVerts = view.getUint16(offset+0x24, true);
head.numSurfaces = view.getUint16(offset+0x26, true);
head.numTriangles = view.getUint16(offset+0x28, true);
head.numQuads = view.getUint16(offset+0x2A, true);
head.bboxX = view.getInt16(offset+0x2C, true)/4096;
head.bboxY = view.getInt16(offset+0x2E, true)/4096;
head.bboxZ = view.getInt16(offset+0x30, true)/4096;
head.bboxWidth = view.getInt16(offset+0x32, true)/4096;
head.bboxHeight = view.getInt16(offset+0x34, true)/4096;
head.bboxDepth = view.getInt16(offset+0x36, true)/4096;
//head.runtimeData = view.getUint64(offset+0x38, true);
texPalOff = head.materialsOffset; //leak into local scope so it can be used by tex and pal bindings
var objects = nitro.read3dInfo(view, offset+0x40, objInfoHandler);
var polys = nitro.read3dInfo(view, head.polyStartOffset, polyInfoHandler);
materials = nitro.read3dInfo(view, head.materialsOffset+4, matInfoHandler);
var tex = nitro.read3dInfo(view, head.materialsOffset+view.getUint16(head.materialsOffset, true), texInfoHandler);
var palt = nitro.read3dInfo(view, head.materialsOffset+view.getUint16(head.materialsOffset+2, true), palInfoHandler);
var commands = parseBones(head.bonesOffset, view, polys, materials, objects, head.maxStack);
return {head: head, objects: objects, polys: polys, materials: materials, tex:tex, palt:palt, commands:commands}
}
function parseBones(offset, view, polys, materials, objects, maxStack) {
var last;
var commands = [];
var freeStack = maxStack;
var forceID=null;
var lastMat = null;
while (offset<texPalOff) { //bones
last = view.getUint8(offset++);
console.log("bone cmd: 0x"+last.toString(16));
switch (last) {
case 0x06: //bind object transforms to parent. bone exists but is not placed in the stack
var obj = view.getUint8(offset++);
var parent = view.getUint8(offset++);
var zero = view.getUint8(offset++);
var object = objects.objectData[obj];
object.parent = parent;
commands.push({obj:obj, parent:parent, stackID:freeStack++});
break;
case 0x26:
case 0x46: //placed in the stack at stack id
var obj = view.getUint8(offset++);
var parent = view.getUint8(offset++);
var zero = view.getUint8(offset++);
var stackID = view.getUint8(offset++);
var object = objects.objectData[obj];
object.parent = parent;
commands.push({obj:obj, parent:parent, stackID:((last == 0x26)?stackID:freeStack++), restoreID:((last == 0x46)?stackID:null)});
break;
case 0x66: //has ability to restore to another stack id. no idea how this works
var obj = view.getUint8(offset++);
var parent = view.getUint8(offset++);
var zero = view.getUint8(offset++);
var stackID = view.getUint8(offset++);
var restoreID = view.getUint8(offset++);
var object = objects.objectData[obj];
object.parent = parent;
commands.push({obj:obj, parent:parent, stackID:stackID, restoreID:restoreID});
break;
case 0x04:
case 0x24:
case 0x44: //bind material to polygon: matID, 5, polyID
var mat = view.getUint8(offset++);
lastMat = mat;
var five = view.getUint8(offset++); //???
var poly = view.getUint8(offset++);
polys.objectData[poly].stackID = (forceID == null)?(commands[commands.length-1].stackID):forceID;
polys.objectData[poly].mat = mat;
break;
case 1:
break;
case 2: //node visibility (maybe to implement this set matrix to 0)
var node = view.getUint8(offset++);
var vis = view.getUint8(offset++);
objects.objectData[node].vis = vis;
console.log("visibility thing "+node);
if (node > 10) debugger;
break;
case 3: //stack id for poly (wit)
forceID = view.getUint8(offset++);
console.log("stackid is "+forceID);
case 0:
break;
case 5:
//i don't... what??
//holy shp!
var poly = view.getUint8(offset++);
polys.objectData[poly].stackID = (stackID == null)?(commands[commands.length-1].forceID):forceID;
polys.objectData[poly].mat = lastMat;
break;
case 7:
//sets object to be billboard
var obj = view.getUint8(offset++);
objects.objectData[obj].billboardMode = 1;
mainObj.hasBillboards = true;
break;
case 8:
//sets object to be billboard around only y axis. (xz remain unchanged)
var obj = view.getUint8(offset++);
objects.objectData[obj].billboardMode = 2;
mainObj.hasBillboards = true;
break;
case 0x0b:
break; //begin, not quite sure what of. doesn't seem to change anything
case 0x2b:
break; //end
default:
console.log("bone transform unknown: "+last);
break;
}
}
//if (window.throwWhatever) debugger;
return commands;
}
function matInfoHandler(view, off, base) {
var offset = texPalOff + view.getUint32(off, true);
var rel = 0;
/*while (rel < 40) {
var flags = view.getUint16(offset+rel, true);
if ((flags&15)==15) console.log("rel at "+rel);
rel += 2;
}*/
var polyAttrib = view.getUint16(offset+12, true);
console.log(polyAttrib);
var flags = view.getUint16(offset+22, true); //other info in here is specular data etc.
//scale starts at 44;
var mat;
offset += 44;
switch ((flags>>14) & 0x03) { //texture scaling mode
case 0:
mat = mat3.create(); //no scale
break;
case 1:
mat = mat3.create();
mat3.scale(mat, mat, [view.getInt32(offset, true)/4096, view.getInt32(offset+4, true)/4096]);
//mat3.translate(mat, mat, [-anim.translateS[(texFrame>>anim.frameStep.translateS)%anim.translateS.length], anim.translateT[(texFrame>>anim.frameStep.translateT)%anim.translateT.length]]) //for some mystery reason I need to negate the S translation
break;
case 2:
case 3:
mat = mat3.create(); //custom tex mat
alert("custom");
for (var i=0; i<16; i++) {
mat[i] = view.getInt32(offset, true)/4096;
offset += 4;
}
}
var cullMode = ((polyAttrib>>6)&3);
var alpha = ((polyAttrib>>16)&31)/31;
if (alpha == 0) alpha = 1;
return {
height: 8 << ((flags>>7)&7),
width: 8 << ((flags>>4)&7),
repeatX: flags&1,
repeatY: (flags>>1)&1,
flipX: (flags>>2)&1,
flipY: (flags>>3)&1,
texMat: mat,
alpha: alpha,
cullMode: cullMode,
nextoff: off + 4
}
}
function texInfoHandler(view, off, base, ind) {
var oDat = texPalOff+view.getUint16(off, true); //contains offset to array of materials to bind to
var num = view.getUint8(off+2, true);
var mats = [];
for (var i=0; i<num; i++) {
var mat = view.getUint8(oDat++);
materials.objectData[mat].tex = ind; //bind to this material
mats.push(mat);
}
return {
mats: mats,
nextoff: off + 4
}
}
function palInfoHandler(view, off, base, ind) {
var oDat = texPalOff+view.getUint16(off, true); //contains offset to array of materials to bind to
var num = view.getUint8(off+2, true);
var mats = [];
for (var i=0; i<num; i++) {
var mat = view.getUint8(oDat++);
materials.objectData[mat].pal = ind; //bind to this material
mats.push(mat);
}
return {
mats: mats,
nextoff: off + 4
}
}
function polyInfoHandler(view, off, base) {
var offset = base + view.getUint32(off, true);
var dlStart = offset+view.getUint32(offset+8, true);
var displayList = view.buffer.slice(dlStart, dlStart+view.getUint32(offset+0xC, true))
return {
nextoff: off + 4,
disp: displayList
}
}
function objInfoHandler(view, off, base) {
var offset = base + view.getUint32(off, true);
var flag = view.getUint16(offset, true); //flag format nnnn psrt
var rotTerm1 = view.getInt16(offset+0x2, true)/4096; //first term of rotate mat if present
var translate = vec3.create();
if (!(flag&1)) { //translate (t) flag is 0
translate[0] = view.getInt32(offset+0x4, true)/4096;
translate[1] = view.getInt32(offset+0x8, true)/4096;
translate[2] = view.getInt32(offset+0xC, true)/4096;
offset += 0xC;
}
var pivot;
var A, B, neg, mode;
if (flag&8) { //pivot exists
pivot = new Float32Array([0,0,0,0,0,0,0,0,0]);
mode = (flag>>4)&15;
neg = (flag>>8)&15;
A = view.getInt16(offset+0x4, true)/4096;
B = view.getInt16(offset+0x6, true)/4096;
pivot[mode] = (neg&1)?-1:1;
var horiz = mode%3;
var vert = Math.floor(mode/3)
var left = (horiz==0)?1:0; var top = ((vert==0)?1:0)*3;
var right = (horiz==2)?1:2; var btm = ((vert==2)?1:2)*3;
pivot[left+top] = A;
pivot[right+top] = B;
pivot[left+btm] = (neg&2)?-B:B;
pivot[right+btm] = (neg&4)?-A:A;
offset += 4;
} else {
pivot = mat3.create()
}
var scale = vec3.create();
if (!(flag&4)) {
scale[0] = view.getInt32(offset+0x4, true)/4096;
scale[1] = view.getInt32(offset+0x8, true)/4096;
scale[2] = view.getInt32(offset+0xC, true)/4096;
offset += 0xC;
} else {
scale[0] = 1;
scale[1] = 1;
scale[2] = 1;
}
if ((!(flag&8)) && (!(flag&2))) { //rotate matrix, replaces pivot
pivot[0] = rotTerm1;
pivot[1] = view.getInt16(offset+0x4, true)/4096;
pivot[2] = view.getInt16(offset+0x6, true)/4096;
pivot[3] = view.getInt16(offset+0x8, true)/4096;
pivot[4] = view.getInt16(offset+0xA, true)/4096;
pivot[5] = view.getInt16(offset+0xC, true)/4096;
pivot[6] = view.getInt16(offset+0xE, true)/4096;
pivot[7] = view.getInt16(offset+0x10, true)/4096;
pivot[8] = view.getInt16(offset+0x12, true)/4096;
offset += 16;
}
var mat = mat4.create();
mat4.translate(mat, mat, translate);
mat4.multiply(mat, mat, mat4FromMat3(pivot));
mat4.scale(mat, mat, scale);
return {
translate: translate,
pivot: pivot,
pA: A,
pB: B,
pMode: mode,
pNeg: neg,
scale: scale,
flag: flag,
mat: mat,
billboardMode: 0,
nextoff: off + 4
}
}
function mat4FromMat3(mat) {
dest = mat4.create();
dest[0] = mat[0];
dest[1] = mat[1];
dest[2] = mat[2];
dest[3] = 0;
dest[4] = mat[3];
dest[5] = mat[4];
dest[6] = mat[5];
dest[7] = 0;
dest[8] = mat[6];
dest[9] = mat[7];
dest[10] = mat[8];
dest[11] = 0;
dest[12] = 0;
dest[13] = 0;
dest[14] = 0;
dest[15] = 1;
return dest;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+142
View File
@@ -0,0 +1,142 @@
//
// nsbta.js
//--------------------
// Reads NSBTA files (texture uv animation via uv transform matrices within a polygon) for use in combination with an NSBMD (model) file
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js
//
// man oh man if only there were some documentation on this that weren't shoddily written code in mkds course modifier
// well i guess we can find out how the format works
// together :')
window.nsbta = function(input) {
var mainOff;
var animData;
var mainObj = this;
var prop = [
"scaleS",
"scaleT",
"rotation",
"translateS",
"translateT"
]
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var header = null;
var offset = 0;
var tex;
//nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "BTA0") throw "NSBTA invalid. Expected BTA0, found "+header.stamp;
if (header.numSections > 1) throw "NSBTA invalid. Too many sections - should have 1 maximum.";
offset = header.sectionOffsets[0];
//end nitro
mainOff = offset;
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (stamp != "SRT0") throw "NSBTA invalid. Expected SRT0, found "+stamp;
animData = nitro.read3dInfo(view, mainOff+8, animInfoHandler);
mainObj.animData = animData;
}
function animInfoHandler(view, offset) {
var animOff = view.getUint32(offset, true);
var off = mainOff+animOff;
var obj = readAnimData(view, off);
obj.nextoff = offset+4;
return obj;
}
function readAnimData(view, offset) {
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3); //should be M_AT, where _ is a 0 character
var unknown1 = view.getUint16(offset+4, true);
var unknown2 = view.getUint8(offset+6, false);
var unknown3 = view.getUint8(offset+7, false);
var data = nitro.read3dInfo(view, offset+8, matInfoHandler);
return {data: data, nextoff: data.nextoff};
}
function matInfoHandler(view, offset, base) {
// there doesn't seem to be any documentation on this so I'm going to take the first step and maybe explain a few things here:
// each material has 5 sets of 16 bit values of the following type:
//
// frames: determines the number of frames worth of transforms of this type are stored
// flags: if >4096 then multiple frames are used instead of inline data. not much else is known
// offset/data: depending on previous flag, either points to an array of data or provides the data for the sole frame. relative to base of this 3dinfoobject
// data2: used for rotation matrix (second value)
//
// order is as follows:
// scaleS, scaleT, rotation, translateS, translateT (all values are signed fixed point 1.3.12)
//
// note: rotation external data has two 16 bit integers instead of one per frame.
//
// also!! rotation matrices work as follows:
//
// | B A |
// | -A B |
//
// kind of like nsbmd pivot
var obj = {}
obj.flags = []; //for debug
obj.frames = [];
obj.frameStep = {};
for (var i=0; i<5; i++) {
obj[prop[i]] = [];
var frames = view.getUint16(offset, true);
var flags = view.getUint16(offset+2, true);
var value = view.getUint16(offset+4, true);
var data2 = view.getInt16(offset+6, true)/4096;
//flags research so far:
//bit 13 (8196) - set if inline single frame data, unset if multiple frame data at offset
//bit 14-15 - framestep, aka what to shift frame counters by (eg for half framerate this would be 1, frame>>1, essentially dividing the frame speed by 2.)
obj.frameStep[prop[i]] = (flags>>14);
obj.flags[i] = flags;
obj.frames[i] = frames;
if (flags & 8192) {
if (value & 32768) value = 65536-value; //convert to int
obj[prop[i]].push(value/4096);
if (i == 2) obj[prop[i]].push(data2);
} else { //data is found at offset
frames = frames>>obj.frameStep[prop[i]];
//frames -= 1;
var off = base + value-8;
for (var j=0; j<frames*((i==2)?2:1); j++) {
var prevvalue = view.getInt16(off-2, true)/4096;
//debugger;
obj[prop[i]].push(view.getInt16(off, true)/4096);
off += 2;
}
}
offset += 8;
}
obj.nextoff = offset;
return obj;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+158
View File
@@ -0,0 +1,158 @@
//
// nsbtp.js
//--------------------
// Reads NSBTP files (texture info animation) for use in combination with an NSBMD (model) file
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js
//
window.nsbtp = function(input) {
var mainOff, matOff;
var animData;
//anim data structure:
// {
// objectData: [
// {
// obj: { }
// }
// ]
// }
var mainObj = this;
var prop = [
"scaleS",
"scaleT",
"rotation",
"translateS",
"translateT"
]
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var header = null;
var offset = 0;
var tex;
//nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "BTP0") throw "NSBTP invalid. Expected BTP0, found "+header.stamp;
if (header.numSections > 1) throw "NSBTP invalid. Too many sections - should have 1 maximum.";
offset = header.sectionOffsets[0];
//end nitro
mainOff = offset;
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (stamp != "PAT0") throw "NSBTP invalid. Expected PAT0, found "+stamp;
animData = nitro.read3dInfo(view, mainOff+8, animInfoHandler);
debugger;
mainObj.animData = animData;
}
function animInfoHandler(view, offset) {
var animOff = view.getUint32(offset, true);
var off = mainOff+animOff;
var obj = readAnimData(view, off);
obj.nextoff = offset+4;
return obj;
}
function readAnimData(view, offset) {
matOff = offset;
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3); //should be M_PT, where _ is a 0 character
offset += 4;
//b400 0303 4400 7400 - countdown (3..2..1.. then start is another model, duration 180 frames, 3 frames of anim)
//1400 0404 4800 8800 - kuribo (4 frames, shorter animation duration)
//1e00 0202 4000 6000 - pinball stage (2 frames)
//0200 0202 4000 6000 - fish, cow and crab (duration and total 2 frames, unusually short animation)
//0d00 0404 5000 9000 - bat (duration 13, 6 frames, uneven pacing)
//16bit duration (60fps frames, total)
//8bit tex start
//8bit pal start
//16bit unknown (flags? kuribo repeats by playing backwards)
//16bit unknown
//example data, for 3 mat 3 pal data
//var tinfo = texInfoHandler(view, offset+4);
//8 bytes here? looks like texinfo
var duration = view.getUint16(offset, true);
var tframes = view.getUint8(offset+2);
var pframes = view.getUint8(offset+3);
var unknown = view.getUint16(offset+4, true);
var unknown2 = view.getUint16(offset+6, true);
//...then another nitro
var data = nitro.read3dInfo(view, offset+8, matInfoHandler);
return {data: data, nextoff: data.nextoff, tframes:tframes, pframes:pframes, unknown:unknown, unknown2:unknown2, duration:duration};
}
function matInfoHandler(view, offset, base) {
var obj = {}
obj.frames = [];
// in here...
// 16bit frames
// 16bit maybe material number (probably? mostly 0) to replace
// 16bit unknown (flags? 0x4400 count, 0x1101 waluigi, 0x3303 goomba, 0x0010 fish)
// 16bit offset from M_PT (always 0x38)
//at offset (frame of these)
// 16bit happenAt
// 8bit tex
// 8bit pal
//then (frame of these)
// 16char texname
//then (frame of these)
// 16char palname
var frames = view.getUint16(offset, true);
obj.matinfo = view.getUint16(offset+2, true);
obj.flags = view.getUint16(offset+4, true);
var offset2 = view.getUint16(offset+6, true);
offset += 8;
obj.nextoff = offset;
offset = matOff + offset2;
//info and timing for each frame
for (var i=0; i<frames; i++) {
obj.frames[i] = {
time: view.getUint16(offset, true),
tex: view.getUint8(offset+2),
mat: view.getUint8(offset+3),
}
offset += 4;
}
//read 16char tex names
for (var i=0; i<frames; i++) {
}
//read 16char pal names
for (var i=0; i<frames; i++) {
}
return obj;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+277
View File
@@ -0,0 +1,277 @@
//
// nsbtx.js
//--------------------
// Reads NSBTX files (or TEX0 sections) and provides canvases containing decoded texture data.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js
//
window.nsbtx = function(input, tex0) {
var texDataSize, texInfoOff, texOffset, compTexSize, compTexInfoOff,
compTexOffset, compTexInfoDataOff /*wtf*/, palSize, palInfoOff,
palOffset, mainOff
var textureInfo, paletteInfo, palData, texData, compData, compInfoData, colourBuffer
var thisObj = this;
var bitDepths = [0, 8, 2, 4, 8, 2, 8, 16]
if (input != null) {
load(input, tex0);
}
this.load = load;
this.readTexWithPal = readTexWithPal;
this.cache = {}; //textures for btx are cached in this object.
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(input, tex0) {
colourBuffer = new Uint32Array(4);
var view = new DataView(input);
var header = null;
var offset = 0;
if (!tex0) { //nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "BTX0") throw "nsbtx invalid. Expected BTX0, found "+header.stamp;
if (header.numSections > 1) throw "NSBTX invalid. Too many sections - should only have one.";
offset = header.sectionOffsets[0];
}
mainOff = offset;
var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (stamp != "TEX0") throw "NSBTX invalid. Expected TEX0, found "+stamp;
var size = view.getUint32(offset+0x04, true);
texDataSize = view.getUint16(offset+0x0C, true)<<3;
texInfoOff = view.getUint16(offset+0x0E, true);
texOffset = view.getUint16(offset+0x14, true);
compTexSize = view.getUint16(offset+0x1C, true)<<3;
compTexInfoOff = view.getUint16(offset+0x1E, true);
compTexOffset = view.getUint32(offset+0x24, true);
compTexInfoDataOff = view.getUint32(offset+0x28, true);
palSize = view.getUint32(offset+0x30, true)<<3;
palInfoOff = view.getUint32(offset+0x34, true);
palOffset = view.getUint32(offset+0x38, true);
//read palletes, then textures.
var po = mainOff + palOffset;
palData = input.slice(po, po+palSize);
var to = mainOff + texOffset;
texData = input.slice(to, to+texDataSize);
var co = mainOff + compTexOffset;
compData = input.slice(co, co+compTexSize); //pixel information for compression. 2bpp, 16 pixels, so per 4x4 block takes up 4 bytes
var cio = mainOff + compTexInfoDataOff;
compInfoData = input.slice(cio, cio+compTexSize/2); //each 4x4 block has a 16bit information uint. 2 bytes per block, thus half the size of above.
paletteInfo = nitro.read3dInfo(view, mainOff + palInfoOff, palInfoHandler);
textureInfo = nitro.read3dInfo(view, mainOff + texInfoOff, texInfoHandler);
thisObj.paletteInfo = paletteInfo;
thisObj.textureInfo = textureInfo;
}
function readTexWithPal(textureId, palId) {
var tex = textureInfo.objectData[textureId];
var pal = paletteInfo.objectData[palId];
var format = tex.format;
var trans = tex.pal0trans;
if (format == 5) return readCompressedTex(tex, pal); //compressed 4x4 texture, different processing entirely
var off = tex.texOffset;
var palView = new DataView(palData);
var texView = new DataView(texData);
var palOff = pal.palOffset;
var canvas = document.createElement("canvas");
canvas.width = tex.width;
canvas.height = tex.height;
var ctx = canvas.getContext("2d");
var img = ctx.getImageData(0, 0, tex.width, tex.height);
var total = tex.width*tex.height;
var databuf;
for (var i=0; i<total; i++) {
var col;
if (format == 1) { //A3I5 encoding. 3 bits alpha 5 bits pal index
var dat = texView.getUint8(off++)
col = readPalColour(palView, palOff, dat&31, trans);
col[3] = (dat>>5)*(255/7);
} else if (format == 2) { //2 bit pal
if (i%4 == 0) databuf = texView.getUint8(off++);
col = readPalColour(palView, palOff, (databuf>>((i%4)*2))&3, trans)
} else if (format == 3) { //4 bit pal
if (i%2 == 0) {
databuf = texView.getUint8(off++);
col = readPalColour(palView, palOff, databuf&15, trans)
} else {
col = readPalColour(palView, palOff, databuf>>4, trans)
}
} else if (format == 4) { //8 bit pal
col = readPalColour(palView, palOff, texView.getUint8(off++), trans)
} else if (format == 6) { //A5I3 encoding. 5 bits alpha 3 bits pal index
var dat = texView.getUint8(off++)
col = readPalColour(palView, palOff, dat&7, trans);
col[3] = (dat>>3)*(255/31);
} else if (format == 7) { //raw color data
col = texView.getUint16(off, true);
colourBuffer[0] = Math.round(((col&31)/31)*255)
colourBuffer[1] = Math.round((((col>>5)&31)/31)*255)
colourBuffer[2] = Math.round((((col>>10)&31)/31)*255)
colourBuffer[3] = Math.round((col>>15)*255);
col = colourBuffer;
off += 2;
} else {
console.log("texture format is none, ignoring")
return canvas;
}
img.data.set(col, i*4);
}
ctx.putImageData(img, 0, 0)
return canvas;
}
function readCompressedTex(tex, pal) { //format 5, 4x4 texels. I'll keep this well documented so it's easy to understand.
var off = tex.texOffset;
var texView = new DataView(compData); //real texture data - 32 bits per 4x4 block (one byte per 4px horizontal line, each descending 1px)
var compView = new DataView(compInfoData); //view into compression info - informs of pallete and parameters.
var palView = new DataView(palData); //view into the texture pallete
var compOff = off/2; //info is 2 bytes per block, so the offset is half that of the tex offset.
var palOff = pal.palOffset;
var transColor = new Uint8Array([0, 0, 0, 0]); //transparent black
var canvas = document.createElement("canvas");
canvas.width = tex.width;
canvas.height = tex.height;
var ctx = canvas.getContext("2d");
var img = ctx.getImageData(0, 0, tex.width, tex.height);
var w = tex.width>>2; //iterate over blocks, block w and h is /4.
var h = tex.height>>2;
for (var y=0; y<h; y++) {
for (var x=0; x<w; x++) {
//inside block
var bInfo = compView.getUint16(compOff, true); //block info
var addr = (bInfo & 0x3fff); //offset to relevant pallete
var mode = ((bInfo >> 14) & 3);
var finalPo = palOff+addr*4;
var imgoff = x*4+(y*w*16);
for (var iy=0; iy<4; iy++) {
var dat = texView.getUint8(off++);
for (var ix=0; ix<4; ix++) { //iterate over horiz lines
var part = (dat>>(ix*2))&3;
var col;
switch (mode) {
case 0: //value 3 is transparent, otherwise pal colour
if (part == 3) col = transColor;
else col = readPalColour(palView, finalPo, part);
break;
case 1: //average mode - colour 2 is average of 1st two, 3 is transparent. 0&1 are normal.
if (part == 3) col = transColor;
else if (part == 2) col = readFractionalPal(palView, finalPo, 0.5);
else col = readPalColour(palView, finalPo, part);
break;
case 2: //pal colour
col = readPalColour(palView, finalPo, part);
break;
case 3: //5/8 3/8 mode - colour 2 is 5/8 of col0 plus 3/8 of col1, 3 is 3/8 of col0 plus 5/8 of col1. 0&1 are normal.
if (part == 3) col = readFractionalPal(palView, finalPo, 3/8);
else if (part == 2) col = readFractionalPal(palView, finalPo, 5/8);
else col = readPalColour(palView, finalPo, part);
break;
}
img.data.set(col, (imgoff++)*4)
}
imgoff += tex.width-4;
}
compOff += 2; //align off to next block
}
}
ctx.putImageData(img, 0, 0)
return canvas;
}
function readPalColour(view, palOff, ind, pal0trans) {
var col = view.getUint16(palOff+ind*2, true);
var f = 255/31;
colourBuffer[0] = Math.round((col&31)*f)
colourBuffer[1] = Math.round(((col>>5)&31)*f)
colourBuffer[2] = Math.round(((col>>10)&31)*f)
colourBuffer[3] = (pal0trans && ind == 0)?0:255;
return colourBuffer;
}
function readFractionalPal(view, palOff, i) {
var col = view.getUint16(palOff, true);
var col2 = view.getUint16(palOff+2, true);
var ni = 1-i;
var f = 255/31;
colourBuffer[0] = Math.round((col&31)*f*i + (col2&31)*f*ni)
colourBuffer[1] = Math.round(((col>>5)&31)*f*i + ((col2>>5)&31)*f*ni)
colourBuffer[2] = Math.round(((col>>10)&31)*f*i + ((col2>>10)&31)*f*ni)
colourBuffer[3] = 255;
return colourBuffer;
}
function palInfoHandler(view, offset) {
var palOffset = view.getUint16(offset, true)<<3;
var unknown = view.getUint16(offset+2, true);
return {
palOffset: palOffset,
unknown: unknown,
nextoff: offset+4
}
}
function texInfoHandler(view, offset) {
var texOffset = view.getUint16(offset, true)<<3;
var flags = view.getUint16(offset+2, true);
var width2 = view.getUint8(offset+4, true);
var unknown = view.getUint8(offset+5, true);
var height2 = view.getUint8(offset+6, true);
var unknown2 = view.getUint8(offset+7, true);
return {
texOffset: texOffset,
pal0trans: (flags>>13)&1, //two top flags are texture matrix modes. not sure if it really matters (except for nsbta animation maybe, but 0 = no transform and things that have tex animations are set to 0 anyways).
format: ((flags>>10)&7),
height: 8 << ((flags>>7)&7),
width: 8 << ((flags>>4)&7),
repeatX: flags&1,
repeatY: (flags>>1)&1,
flipX: (flags>>2)&1,
flipY: (flags>>3)&1,
unkWidth: width2,
unk1: unknown,
unkHeight: height2,
unk2: unknown2,
nextoff: offset+8
}
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+102
View File
@@ -0,0 +1,102 @@
//
// sbnk.js
//--------------------
// Reads sbnk files.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
//
window.sbnk = function(input, dataView) {
var t = this;
this.load = load;
function load(input, dataView) {
var view = (dataView)?input:(new DataView(input));
var header = null;
var offset = 0;
var stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
if (stamp != "SBNK") throw "SWAV invalid. Expected SWAV, found "+stamp;
offset += 16;
var data = readChar(view, offset)+readChar(view, offset+1)+readChar(view, offset+2)+readChar(view, offset+3);
if (data != "DATA") throw "SWAV invalid, expected DATA, found "+data;
offset += 8;
offset += 32; //skip reserved
var numInst = view.getUint32(offset, true);
t.instruments = [];
offset += 4;
for (var i=0; i<numInst; i++) {
var fRecord = view.getUint8(offset);
var nOffset = view.getUint16(offset+1, true);
if (fRecord == 0) {
t.instruments.push({type:0});
} else if (fRecord < 16) { //note/wave definition
var obj = readParams(view, nOffset);
obj.type = 1;
t.instruments.push(obj);
} else if (fRecord == 16) {
var obj = {};
obj.lower = view.getUint8(nOffset++);
obj.upper = view.getUint8(nOffset++);
obj.entries = []
var notes = (obj.upper-obj.lower)+1;
for (var j=0; j<notes; j++) {
obj.entries.push(readParams(view, nOffset+2));
nOffset += 12;
}
obj.type = 2;
t.instruments.push(obj);
} else if (fRecord == 17) {
var obj = {};
obj.regions = [];
for (var j=0; j<8; j++) {
var dat = view.getUint8(nOffset+j);
if (dat != 0) obj.regions.push(dat);
else break;
}
obj.entries = [];
nOffset += 8;
for (var j=0; j<obj.regions.length; j++) {
obj.entries.push(readParams(view, nOffset+2));
nOffset += 12;
}
obj.type = 3;
t.instruments.push(obj);
}
offset += 4;
}
}
function readParams(view, off) {
var obj = {};
obj.swav = view.getUint16(off, true);
obj.swar = view.getUint16(off+2, true);
obj.note = view.getUint8(off+4);
obj.freq = noteToFreq(obj.note);
obj.attack = view.getUint8(off+5);
obj.decay = view.getUint8(off+6);
obj.sustainLvl = view.getUint8(off+7);
obj.release = view.getUint8(off+8);
obj.pan = view.getUint8(off+9);
return obj;
}
function noteToFreq(n) {
return Math.pow(2, (n-49)/12)*440;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
if (input != null) {
load(input, dataView);
}
}
+168
View File
@@ -0,0 +1,168 @@
//
// sdat.js
//--------------------
// Reads sdat archives.
// Right now this just loads literally every resource in the sdat since in js there is no such thing as half loading a
// file from local storage, so why not just load it once and store in a usable format.
//
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
//
window.sdat = function(input) {
var t = this;
this.sections = {};
this.load = load;
function load(input) {
t.buffer = input;
var view = new DataView(input);
var header = null;
var offset = 0;
var stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
if (stamp != "SDAT") throw "SDAT invalid. Expected SDAT, found "+stamp;
var unknown1 = view.getUint32(0x4, true);
var filesize = view.getUint32(0x8, true);
var headsize = view.getUint16(0xC, true);
var numSections = view.getUint16(0xE, true);
var sectionOffsets = [];
var sectionSizes = [];
for (var i=3; i>-1; i--) { //reverse order so we can process files into js objects
var off = (view.getUint32(0x10+i*8, true));
var size = (view.getUint32(0x14+i*8, true));
if (size != 0) readSection(view, off);
}
}
function readSection(view, off) {
var stamp = "$"+readChar(view, off)+readChar(view, off+1)+readChar(view, off+2)+readChar(view, off+3);
if (sectionFunc[stamp] != null) t.sections[stamp] = sectionFunc[stamp](view, off+8);
else console.error("Invalid section in SDAT! No handler for section type "+stamp.substr(1, 4));
}
var sectionFunc = {}
sectionFunc["$SYMB"] = function(view, off) {
}
sectionFunc["$INFO"] = function(view, off) {
var obj = [];
for (var i=0; i<8; i++) {
var relOff = off+view.getUint32(off+i*4, true)-8;
var count = view.getUint32(relOff, true);
obj[i] = [];
relOff += 4;
var last = null;
for (var j=0; j<count; j++) {
var infoOff = view.getUint32(relOff, true);
//WRONG
last = recordInfoFunc[i](view, off+infoOff-8);//(infoOff == 0 && last != null)?last.nextOff:(off+infoOff-8));
obj[i][j] = last;
relOff += 4;
}
}
console.log(obj);
return obj;
}
sectionFunc["$FAT "] = function(view, off) {
var a = [];
var count = view.getUint32(off, true);
off += 4;
for (var i=0; i<count; i++) {
var obj = {};
obj.off = view.getUint32(off, true);
obj.size = view.getUint32(off+4, true);
off += 16;
a.push(obj);
}
console.log(a);
return a;
}
sectionFunc["$FILE"] = function(view, off) {
console.log("file");
}
var recordInfoFunc = []
recordInfoFunc[0] = function(view, off) {
var obj = {};
obj.fileID = view.getUint16(off, true);
obj.seq = new sseq(getFile(obj.fileID));
obj.pc = 0;
obj.unknown = view.getUint16(off+2, true);
obj.bank = view.getUint16(off+4, true);
obj.vol = view.getUint8(off+6);
obj.cpr = view.getUint8(off+7);
obj.ppr = view.getUint8(off+8);
obj.ply = view.getUint8(off+9);
obj.nextOff = off+10;
return obj;
}
recordInfoFunc[1] = function(view, off) {
var obj = {};
obj.fileID = view.getUint16(off, true);
obj.arc = new ssar(getFile(obj.fileID));
obj.unknown = view.getUint16(off+2, true);
obj.nextOff = off+4;
return obj;
}
recordInfoFunc[2] = function(view, off) {
var obj = {};
obj.fileID = view.getUint16(off, true);
obj.unknown = view.getUint16(off+2, true);
obj.bank = new sbnk(getFile(obj.fileID));
obj.waveArcs = [];
off += 4;
for (var i=0; i<4; i++) {
obj.waveArcs[i] = view.getUint16(off, true);
off += 2;
}
obj.nextOff = off;
return obj;
}
recordInfoFunc[3] = function(view, off) {
var obj = {};
obj.fileID = view.getUint16(off, true);
obj.unknown = view.getUint16(off+2, true);
obj.arc = new swar(getFile(obj.fileID));
obj.nextOff = off+4;
return obj;
}
recordInfoFunc[4] = function(view, off) {}
recordInfoFunc[5] = function(view, off) {}
recordInfoFunc[6] = function(view, off) {}
recordInfoFunc[7] = function(view, off) {
var obj = {};
obj.fileID = view.getUint16(off, true);
obj.unknown = view.getUint16(off+2, true);
obj.vol = view.getUint8(off+4);
obj.pri = view.getUint8(off+5);
obj.ply = view.getUint8(off+6);
obj.nextOff = off+7;
return obj;
}
function getFile(fid) {
var file = t.sections["$FAT "][fid];
if (file != null) {
return t.buffer.slice(file.off, file.off+file.size);
}
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
if (input != null) {
load(input);
}
}
+56
View File
@@ -0,0 +1,56 @@
//
// ssar.js
//--------------------
// Reads ssar files.
// by RHY3756547
//
window.ssar = function(input) {
var t = this;
this.load = load;
function load(input) {
var view = new DataView(input);
var header = null;
var offset = 0;
var stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
if (stamp != "SSAR") throw "SSAR invalid. Expected SSAR, found "+stamp;
offset += 16;
var data = readChar(view, offset)+readChar(view, offset+1)+readChar(view, offset+2)+readChar(view, offset+3);
if (data != "DATA") throw "SSAR invalid, expected DATA, found "+data;
offset += 8;
t.dataOff = view.getUint32(offset, true);
t.data = new Uint8Array(view.buffer.slice(t.dataOff));
var count = view.getUint32(offset+4, true);
t.entries = [];
offset += 8;
for (var i=0; i<count; i++) {
t.entries.push(readSeqEntry(view, offset));
offset += 12;
}
}
function readSeqEntry(view, off) {
var obj = {};
obj.pc = view.getUint32(off, true);
obj.seq = {data:t.data};
obj.bank = view.getUint16(off+4, true);
obj.vol = view.getUint8(off+6);
obj.cpr = view.getUint8(off+7);
obj.ppr = view.getUint8(off+8);
obj.ply = view.getUint8(off+9);
return obj;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
if (input != null) {
load(input);
}
}
+34
View File
@@ -0,0 +1,34 @@
//
// sseq.js
//--------------------
// Reads sseq files.
// by RHY3756547
//
window.sseq = function(input) {
var t = this;
this.load = load;
function load(input, archived) {
var view = new DataView(input);
var header = null;
var offset = 0;
var stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
if (stamp != "SSEQ") throw "SSEQ invalid. Expected SSEQ, found "+stamp;
offset += 16;
var data = readChar(view, offset)+readChar(view, offset+1)+readChar(view, offset+2)+readChar(view, offset+3);
if (data != "DATA") throw "SWAV invalid, expected DATA, found "+data;
offset += 8;
t.data = new Uint8Array(input.slice(view.getUint32(offset, true)));
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
if (input != null) {
load(input);
}
}
+43
View File
@@ -0,0 +1,43 @@
//
// swar.js
//--------------------
// Reads swar files.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
//
window.swar = function(input, dataView) {
var t = this;
this.load = load;
function load(input, dataView) {
var view = (dataView)?input:(new DataView(input));
var header = null;
var offset = 0;
var stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
if (stamp != "SWAR") throw "SWAR invalid. Expected SWAR, found "+stamp;
offset += 16; //skip magic number, size and number of blocks
var data = readChar(view, offset)+readChar(view, offset+1)+readChar(view, offset+2)+readChar(view, offset+3);
if (data != "DATA") throw "SWAV invalid, expected DATA, found "+data;
offset += 40; //skip reserved 0s and size
var nSamples = view.getUint32(offset, true);
offset += 4;
t.samples = [];
for (var i=0; i<nSamples; i++) {
t.samples.push(new swav(new DataView(input, view.getUint32(offset, true)), false, true));
offset += 4;
}
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
if (input != null) {
load(input, dataView);
}
}
+128
View File
@@ -0,0 +1,128 @@
//
// swav.js
//--------------------
// Reads swav files.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
//
window.swav = function(input, hasHead, dataView) {
var t = this;
this.load = load;
this.getAudioBuffer = getAudioBuffer;
function load(input, hasHead, dataView) {
var view = (dataView)?input:(new DataView(input));
var header = null;
var offset = 0;
if (hasHead) {
var stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
if (stamp != "SWAV") throw "SWAV invalid. Expected SWAV, found "+stamp;
offset += 16;
var data = readChar(view, offset)+readChar(view, offset+1)+readChar(view, offset+2)+readChar(view, offset+3);
if (data != "DATA") throw "SWAV invalid, expected DATA, found "+data;
offset += 8;
}
t.waveType = view.getUint8(offset);
t.bLoop = view.getUint8(offset+1);
t.nSampleRate = view.getUint16(offset+2, true);
if (t.nSampleRate < 3000) throw "BAD SAMPLE RATE! "+t.nSampleRate;
t.nTime = view.getUint16(offset+4, true);
t.nLoopOff = view.getUint16(offset+6, true);
t.nNonLoopLen = view.getUint32(offset+8, true);
t.bytesize = (t.nLoopOff+t.nNonLoopLen)*4;
t.mul = 1;
offset += 12;
var data;
switch (t.waveType) {
case 0:
var size = t.bytesize;
data = new Float32Array(size);
for (var i=0; i<size; i++) {
data[i] = view.getInt8(offset++)/0x7F;
}
t.loopSTime = (t.nLoopOff*4)/t.nSampleRate;
break;
case 1:
var size = t.bytesize/2;
data = new Float32Array(size);
for (var i=0; i<size; i++) {
data[i] = view.getInt16(offset, true)/0x7FFF;
offset += 2;
}
t.loopSTime = (t.nLoopOff*2)/t.nSampleRate;
break;
case 2:
data = decodeADPCM(view, offset);
t.loopSTime = ((t.nLoopOff-1)*8)/t.nSampleRate;
break;
}
t.data = data;
}
function getAudioBuffer(ctx) {
while (true) {
try {
var buf = ctx.createBuffer(1, t.data.length, t.nSampleRate);
buf.getChannelData(0).set(t.data);
return buf;
} catch (e) {
t.nSampleRate *= 2; //keep increasing sample rate until the target supports it.
t.loopSTime /= 2;
t.mul *= 2;
}
if (t.nSampleRate > 96000) return ctx.createBuffer(1, 1, 44000); //give up and return an empty buffer
}
}
var indChanges = [-1, -1, -1, -1, 2, 4, 6, 8];
var ADPCMTable = [
0x0007,0x0008,0x0009,0x000A,0x000B,0x000C,0x000D,0x000E,0x0010,0x0011,0x0013,0x0015,
0x0017,0x0019,0x001C,0x001F,0x0022,0x0025,0x0029,0x002D,0x0032,0x0037,0x003C,0x0042,
0x0049,0x0050,0x0058,0x0061,0x006B,0x0076,0x0082,0x008F,0x009D,0x00AD,0x00BE,0x00D1,
0x00E6,0x00FD,0x0117,0x0133,0x0151,0x0173,0x0198,0x01C1,0x01EE,0x0220,0x0256,0x0292,
0x02D4,0x031C,0x036C,0x03C3,0x0424,0x048E,0x0502,0x0583,0x0610,0x06AB,0x0756,0x0812,
0x08E0,0x09C3,0x0ABD,0x0BD0,0x0CFF,0x0E4C,0x0FBA,0x114C,0x1307,0x14EE,0x1706,0x1954,
0x1BDC,0x1EA5,0x21B6,0x2515,0x28CA,0x2CDF,0x315B,0x364B,0x3BB9,0x41B2,0x4844,0x4F7E,
0x5771,0x602F,0x69CE,0x7462,0x7FFF
]; //thanks no$gba docs
function decodeADPCM(view, off) {
var pcm = view.getUint16(off, true); //initial pcm
var ind = view.getUint8(off+2); //initial index
off += 4;
var size = t.bytesize-4;
var out = new Float32Array((size*2));
var write = 0;
//out[write++] = pcm/0x7FFF;
for (var i=0; i<size; i++) {
var b = view.getUint8(off++);
for (var j=0; j<2; j++) {
var nibble = (b>>(j*4))&15;
var diff = Math.floor(((nibble&7)*2+1)*ADPCMTable[ind]/8);
if (nibble&8) pcm = Math.max(pcm-diff, -0x7FFF);
else pcm = Math.min(pcm+diff, 0x7FFF);
out[write++] = pcm/0x7FFF;
ind = Math.min(88, Math.max(0, ind + indChanges[nibble&7]));
}
}
return out;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
if (input != null) {
load(input, hasHead, dataView);
}
}