|
function BinaryLoader(file, callback) { |
|
if (file == null || file === undefined) { |
|
throw "File can not be null!"; |
|
} |
|
var readString16 = function (f) { |
|
var length = f.getUint16(); |
|
return f.getString(length); |
|
}; |
|
var readString32 = function (f) { |
|
var length = f.getUint32(); |
|
var string = f.getString(length); |
|
if (string != null && string.length > 4) { |
|
string = string.substring(4); |
|
} else if (string != null && string.length == 4) { |
|
string = ""; |
|
} |
|
return string; |
|
}; |
|
var readVariableDeclaration = function (f) { |
|
var variableDeclaration = new ClassDeclarationVariable(); |
|
var name = readString16(f); |
|
var dataType = readString16(f); |
|
var typeByteSize = f.getUint32(); |
|
var subVariableCount = f.getUint32(); |
|
var subVariables = []; |
|
for (var i = 0; i < subVariableCount; i++) { |
|
subVariables.push(readVariableDeclaration(f)); |
|
} |
|
variableDeclaration.name = name; |
|
variableDeclaration.dataType = dataType; |
|
variableDeclaration.typeByteSize = typeByteSize; |
|
variableDeclaration.variables = subVariables; |
|
return variableDeclaration; |
|
}; |
|
var readVariableData = function (f, variableDeclaration) { |
|
//console.groupCollapsed(); |
|
//console.log("Reading " + variableDeclaration.name + " with type " + variableDeclaration.dataType); |
|
|
|
var dataType = variableDeclaration.dataType; |
|
var data = null; |
|
if (dataType == "int32") { |
|
data = f.getInt32(); |
|
} else if (dataType == "uint32") { |
|
data = f.getUint32(); |
|
} else if (dataType == "float32") { |
|
data = f.getFloat32(); |
|
} else if (dataType == "bool") { |
|
data = f.getInt8() != 0; |
|
} else if (dataType == "int8") { |
|
data = f.getInt8() != 0; |
|
} else if (dataType == "uint8") { |
|
data = f.getUint8() != 0; |
|
} else if (dataType == "matrix44") { |
|
data = [f.getFloat32(), f.getFloat32(), f.getFloat32(), f.getFloat32(), |
|
f.getFloat32(), f.getFloat32(), f.getFloat32(), f.getFloat32(), |
|
f.getFloat32(), f.getFloat32(), f.getFloat32(), f.getFloat32(), |
|
f.getFloat32(), f.getFloat32(), f.getFloat32(), f.getFloat32()]; |
|
} else if (dataType == "vector3") { |
|
data = [f.getFloat32(), f.getFloat32(), f.getFloat32()]; |
|
} else if (dataType == "orientation") { |
|
data = [f.getFloat32(), f.getFloat32(), f.getFloat32()]; |
|
} else if (dataType == "managedobjectptr") { |
|
data = f.getUint64(); |
|
} else if (dataType == "reference") { |
|
data = f.getUint64(); |
|
} else if (dataType == "resourcesymbol") { |
|
data = readString32(f); |
|
} else if (dataType == "string") { |
|
data = readString32(f); |
|
} else if (dataType == "WaterManager") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
data = {}; |
|
data.x = f.getUint8(); |
|
data.y = f.getUint8(); |
|
data.values = []; |
|
for (var i = 0; i < dataSize - 2; i++) { |
|
data.values.push(f.getUint8()); |
|
} |
|
// TODO: Find out PathTileList format |
|
f.seek(start + dataSize); |
|
} else if (dataType == "PathTileList") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
data = []; |
|
for (var i = 0; i < dataSize; i++) { |
|
data.push(f.getUint8()); |
|
} |
|
// TODO: Find out PathTileList format |
|
f.seek(start + dataSize); |
|
} else if (dataType == "flexicachelist") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
data = []; |
|
for (var i = 0; i < dataSize; i++) { |
|
data.push(f.getUint8()); |
|
} |
|
// TODO: Find out flexicachelist format |
|
f.seek(start + dataSize); |
|
} else if (dataType == "waypointlist") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
data = []; |
|
for (var i = 0; i < dataSize; i++) { |
|
data.push(f.getUint8()); |
|
} |
|
// TODO: Find out waypointlist format |
|
f.seek(start + dataSize); |
|
} else if (dataType == "SkirtTrees") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
data = []; |
|
for (var i = 0; i < dataSize; i++) { |
|
data.push(f.getUint8()); |
|
} |
|
// TODO: Find out SkirtTrees format |
|
f.seek(start + dataSize); |
|
} else if (dataType == "managedImage") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
data = {}; |
|
data.unknownA = f.getUint32(); // Flag? |
|
data.unknownB = f.getUint32(); // ? |
|
data.unknownC = f.getUint32(); // Bitflag? |
|
data.pixels = []; |
|
var pixelCount = f.getUint32(); |
|
for (var i = 0; i < pixelCount; i++) { |
|
data.pixels.push(f.getUint32()); // RGBA format, A is almost always 255 AKA opaque |
|
} |
|
|
|
f.seek(start + dataSize); |
|
} else if (dataType == "GE_Terrain") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
data = {}; |
|
data.westToEast = f.getUint8(); |
|
data.northToSouth = f.getUint8(); |
|
data.unknown1 = f.getFloat32(); |
|
data.unknown2 = f.getFloat32(); |
|
data.xTileSize = f.getFloat32(); |
|
data.yTileSize = f.getFloat32(); |
|
|
|
data.tiles = []; |
|
var tileCount = data.westToEast * data.northToSouth; |
|
for (var i = 0; i < tileCount; i++) { |
|
var tile = {}; |
|
tile.cornerAHeight = f.getFloat32(); |
|
tile.CornerBHeight = f.getFloat32(); |
|
tile.cornerCHeight = f.getFloat32(); |
|
tile.cornerDHeight = f.getFloat32(); |
|
tile.unknown1 = f.getFloat32(); |
|
tile.unknown2 = f.getFloat32(); |
|
data.tiles.push(tile); |
|
} |
|
|
|
f.seek(start + dataSize); |
|
} else if (dataType == "graphedValue") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
data = {}; |
|
data.unknown1 = f.getUint32(); |
|
data.unknown2 = f.getUint32(); |
|
data.graphedValues = []; |
|
|
|
var count = f.getUint32(); |
|
for (var i = 0; i < count; i++) { |
|
data.graphedValues.push(f.getUint32()); |
|
} |
|
f.seek(start + dataSize); |
|
} else if (dataType == "BlockingScenery") { |
|
var dataSize = f.getUint32(); |
|
data = []; |
|
for (var i = 0; i < dataSize / 4; i++) { |
|
data.push(f.getUint32()); |
|
} |
|
} else if (dataType == "struct") { |
|
var dataSize = variableDeclaration.typeByteSize != 0 ? variableDeclaration.typeByteSize : f.getUint32(); |
|
var start = f.tell(); |
|
//console.log("Struct with size " + dataSize); |
|
//data = {}; |
|
data = []; |
|
for (var i = 0; i < variableDeclaration.variables.length; i++) { |
|
var subData = readVariableData(f, variableDeclaration.variables[i]); |
|
//data[variableDeclaration.variables[i].name] = subData; |
|
data.push(subData); |
|
} |
|
f.seek(start + dataSize); |
|
} else if (dataType == "pathnodearray") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
data = []; |
|
for (var i = 0; i < dataSize; i++) { |
|
data.push(f.getUint32()); |
|
} |
|
f.seek(start + dataSize); |
|
} else if (dataType == "array") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
var arrayLength = f.getUint32(); |
|
//console.log("Array with size " + dataSize + " and length " + arrayLength); |
|
var arrayItems = []; |
|
for (var i = 0; i < arrayLength; i++) { |
|
var arrayItem = []; |
|
for (var j = 0; j < variableDeclaration.variables.length; j++) { |
|
var subData = readVariableData(f, variableDeclaration.variables[j]); |
|
arrayItem.push(subData); |
|
} |
|
arrayItems.push(arrayItem); |
|
} |
|
data = arrayItems; |
|
f.seek(start + dataSize); |
|
} else if (dataType == "list") { |
|
var dataSize = f.getUint32(); |
|
var start = f.tell(); |
|
var arrayLength = f.getUint32(); |
|
//console.log("List with size " + dataSize + " and length " + arrayLength); |
|
var arrayItems = []; |
|
for (var i = 0; i < arrayLength; i++) { |
|
var arrayItem = []; |
|
for (var j = 0; j < variableDeclaration.variables.length; j++) { |
|
var subData = readVariableData(f, variableDeclaration.variables[j]); |
|
arrayItem.push(subData); |
|
} |
|
arrayItems.push(arrayItem); |
|
} |
|
data = arrayItems; |
|
f.seek(start + dataSize); |
|
} else { |
|
console.log("Unparseable datatype: " + dataType); |
|
throw "UnparseableDatatype exception"; |
|
} |
|
|
|
//console.groupEnd(); |
|
return data; |
|
}; |
|
|
|
|
|
var reader = new FileReader(); |
|
reader.onloadend = function (evt) { |
|
//var R3D = null; |
|
if (evt.target.readyState == FileReader.DONE) { |
|
//console.log(JSON.stringify({data: evt.target.result, size: file.size, fileobj: file})); |
|
|
|
//noinspection JSPotentiallyInvalidConstructorUsage |
|
try { |
|
var fileLength = evt.target.result.length; |
|
var f = new jDataView(evt.target.result); |
|
f._littleEndian = true; |
|
var archive = new Archive(); |
|
|
|
var magicA = f.getUint32(); |
|
var magicB = f.getUint32(); |
|
|
|
if (magicA == 0 && magicB == 0) { |
|
console.log("Archive has header"); |
|
// File contains a header |
|
archive.magicA = magicA; |
|
archive.magicB = magicB; |
|
archive.expansionBitflag = f.getUint8(); |
|
archive.expansionUnknownA = f.getUint8(); |
|
archive.expansionUnknownB = f.getUint8(); |
|
archive.expansionUnknownC = f.getUint8(); |
|
|
|
var headerUnknownCount = (f.getUint32() / 4) - 4; |
|
var headerUnknowns = [headerUnknownCount]; |
|
console.log("headerUnknownCount: " + headerUnknownCount); |
|
for (var i = 0; i < headerUnknownCount; i++) { |
|
headerUnknowns[i] = f.getUint32(); |
|
} |
|
archive.headerUnknowns = headerUnknowns; |
|
|
|
if (archive.expansionBitflag & (1 << 5)) { |
|
console.log("File appears to be from wild, reading wildunknowns") |
|
archive.wildUnknownA = f.getInt32(); |
|
archive.wildUnknownB = f.getInt32(); |
|
archive.wildUnknownC = f.getInt32(); |
|
archive.wildUnknownD = f.getInt32(); |
|
} |
|
} else { |
|
console.log("Archive has no header"); |
|
// File contains no header |
|
f.seek(0); |
|
} |
|
|
|
var classDefinitionCount = f.getUint32(); |
|
var classDeclarations = []; |
|
for (var i = 0; i < classDefinitionCount; i++) { |
|
var classDeclaration = new ClassDeclaration(); |
|
classDeclaration.name = readString16(f); |
|
classDeclarations.push(classDeclaration); |
|
|
|
var rootVariableCount = f.getUint32(); |
|
var rootVariables = []; |
|
for (var j = 0; j < rootVariableCount; j++) { |
|
var variableDeclaration = readVariableDeclaration(f); |
|
rootVariables.push(variableDeclaration); |
|
} |
|
classDeclaration.variables = rootVariables; |
|
} |
|
archive.classDeclarations = classDeclarations; |
|
|
|
var classPrototypeCount = f.getUint32(); |
|
var classPrototypes = []; |
|
for (var i = 0; i < classPrototypeCount; i++) { |
|
var classDeclarationIndex = f.getUint32(); |
|
var reference = f.getUint64(); |
|
var classPrototype = new ClassPrototype(); |
|
classPrototype.classDeclarationIndex = classDeclarationIndex; |
|
classPrototype.reference = new Reference(reference); |
|
|
|
var classDeclaration = archive.classDeclarations[classDeclarationIndex]; |
|
var classDeclarationVariableCount = classDeclaration.variables.length; |
|
var variableValues = []; |
|
for (var j = 0; j < classDeclarationVariableCount; j++) { |
|
var variableDeclaration = classDeclaration.variables[j]; |
|
var data = readVariableData(f, variableDeclaration); |
|
variableValues.push(data); |
|
} |
|
classPrototype.variableValues = variableValues; |
|
|
|
classPrototypes.push(classPrototype); |
|
} |
|
archive.classPrototypes = classPrototypes; |
|
|
|
console.log("Endposition: " + f.tell()); |
|
if (f.tell() <= fileLength - 4) { |
|
archive.checksum = f.getUint32(); |
|
console.log("Checksum: " + archive.checksum); |
|
} |
|
|
|
callback(archive); |
|
} catch (e) { |
|
console.log(e); |
|
callback(null); |
|
} |
|
} |
|
}; |
|
var blob = file.slice(0, file.size); |
|
reader.readAsBinaryString(blob); |
|
} |