Last active
March 10, 2024 02:33
-
-
Save Joeywp/317aad84d3b9633d08ad to your computer and use it in GitHub Desktop.
A very basic RCT3 general archive file reader that stores it's data in a way that makes it possible to edit and eventually even write back a general archive file. It depends on the JavaScript library jDataView.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Archive() { | |
this.magicA = 0; // uint32 | |
this.magicB = 0; // uint32 | |
this.expansionBitflag = 0; //byte 4th bit set = soaked, 5th bit set = wild | |
this.expansionUnknownA = 0; //byte | |
this.expansionUnknownB = 0; //byte | |
this.expansionUnknownC = 0; //byte | |
this.headerUnknowns = new Array(12); //uint32 array. Size is always 12 (given by the preceding number, (uint32/4) -4, except for some files created while in the wild debug mode | |
this.wildUnknownA = 0; //int32, seen -1 and 0. 0 seems to indicate user-saved data | |
this.wildUnknownB = 0; //uint32, seen -1 | |
this.wildUnknownC = 0; //int32, seen 0 | |
this.wildUnknownD = 0; //int32, seen 0 | |
this.classDeclarations = []; | |
this.classPrototypes = []; | |
this.checksum = 0; //uint32, algorithm still unknown | |
} | |
function ClassDeclaration() { | |
this.name = null; //string | |
this.variables = []; | |
} | |
function ClassDeclarationVariable() { | |
this.name = null; //string | |
this.dataType = null; //string | |
this.typeByteSize = 0;//uint32, 0 = variable size | |
this.variables = []; | |
} | |
function ClassPrototype() { | |
this.classDeclarationIndex = 0; | |
this.reference = new Reference(); | |
this.variableValues = []; | |
} | |
function ClassPrototypeValue() { | |
this.value = null; //any type | |
this.subVariables = []; | |
} | |
function Reference(reference) { | |
this.reference = reference;//ulong | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(document).on('drop', function (e) { | |
e.stopPropagation(); | |
e.preventDefault(); | |
$("body").removeClass("drop"); | |
var files = e.originalEvent.dataTransfer.files; | |
//console.log(files[0]); | |
new BinaryLoader(files[0], function (archive) { | |
console.log(archive); | |
if (archive != null) { | |
document.write('<pre>'); | |
for (var i = 0; i < archive.classPrototypes.length; i++) { | |
var classDeclaration = archive.classDeclarations[archive.classPrototypes[i].classDeclarationIndex]; | |
if (classDeclaration.name == 'SavedFireworkDisplay') { | |
document.write(classDeclaration.name + "\n"); | |
console.log(archive.classPrototypes[i]); | |
for (var j = 0; j < archive.classPrototypes[i].variableValues[0].length; j++) { | |
var entry = archive.classPrototypes[i].variableValues[0][j]; | |
document.write("Entry " + entry[0] + "\n"); | |
} | |
} | |
} | |
document.write('</pre>'); | |
} | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment