Skip to content

Instantly share code, notes, and snippets.

@Joeywp
Last active March 10, 2024 02:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Joeywp/317aad84d3b9633d08ad to your computer and use it in GitHub Desktop.
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
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
}
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);
}
$(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