sc4
Version:
A command line utility for automating SimCity 4 modding tasks & modifying savegames
163 lines (162 loc) • 5.27 kB
JavaScript
// # Unknown
// Helper class that we use for easily managing unknown values in data
// structures used by the game.
export default class Unknown extends Array {
bool(value) { this.push(value); return this; }
byte(value) { this.push(value); return this; }
word(value) { this.push(value); return this; }
dword(value = 0) { this.push(value); return this; }
qword(value) { this.push(value); return this; }
float(value) { this.push(value); return this; }
double(value) { this.push(value); return this; }
bytes(value) {
if (Array.isArray(value)) {
this.push(new Uint8Array(value));
}
else {
this.push(value);
}
return this;
}
array(value = []) { this.push(value); return this; }
// ## repeat()
// Helper for repeating a certain pattern a few times.
repeat(n, fn) {
for (let i = 0; i < n; i++) {
fn(this);
}
return this;
}
// ## reader(rs)
reader(rs) {
this.clear();
return new UnknownReader(this, rs);
}
// ## writer(ws)
writer(ws) {
return new UnknownWriter(this, ws);
}
// ## generator()
// Creates a generator function that consumes the unknown one by one. This
// is useful when serializing a structure to a buffer, hereby consuming all
// unknown values.
generator() {
let it = this[Symbol.iterator]();
let result;
const fn = () => {
result = it.next();
if (result.done) {
throw new Error(`The generator of the unknown has been fully consumed already! There is probably a mismatch between the amount of unknowns read and written.`);
}
return result.value;
};
return Object.assign(fn, {
bool: () => fn(),
byte: () => fn(),
word: () => fn(),
dword: () => fn(),
qword: () => fn(),
float: () => fn(),
double: () => fn(),
bytes: () => fn(),
array: () => fn(),
assert: () => {
result = it.next();
if (!result.done) {
throw new Error(`The iterator has not been fully consumed! There is probably a mismatch between the amount of unknowns read and written.`);
}
},
});
}
// ## clear()
// Clears the unknown again. This is useful because the labels that might
// have been set for the values are actually kept. This means that you can
// set up the initial unknown from the constructor with the labels, and then
// the labels don't have to be re-assigned later on when actually parsing
// from a file.
clear() {
this.length = 0;
return this;
}
}
// # UnknownReader
// A helper class to be used when reading unknowns.
class UnknownReader {
unknown;
rs;
constructor(unknown, rs) {
this.unknown = unknown;
this.rs = rs;
}
bool() { this.unknown.bool(this.rs.bool()); }
byte() { this.unknown.byte(this.rs.byte()); }
word() { this.unknown.word(this.rs.word()); }
dword(_expected) { this.unknown.dword(this.rs.dword()); }
qword() { this.unknown.qword(this.rs.qword()); }
float() { this.unknown.float(this.rs.float()); }
double() { this.unknown.double(this.rs.double()); }
bytes(length) { this.unknown.bytes(this.rs.read(length)); }
// ## array()
// Reads in the length of an array as a dword, and then creates
// child-unknowns for every entry.
array(fn) {
let arr = new Array(this.rs.dword());
for (let i = 0; i < arr.length; i++) {
let unknown = arr[i] = new Unknown();
let reader = new UnknownReader(unknown, this.rs);
fn.call(this, reader, i);
}
this.unknown.array(arr);
}
// ## repeat()
// Helper for repeating a certain pattern a few times.
repeat(n, fn) {
for (let i = 0; i < n; i++) {
fn(this);
}
}
}
// # UnkownWriter
class UnknownWriter {
unknown;
ws;
generator;
constructor(unknown, ws) {
this.unknown = unknown;
this.generator = unknown.generator();
this.ws = ws;
}
bool() { this.ws.bool(this.generator.bool()); }
;
byte() { this.ws.byte(this.generator.byte()); }
;
word() { this.ws.word(this.generator.word()); }
;
dword() { this.ws.dword(this.generator.dword()); }
;
qword() { this.ws.qword(this.generator.qword()); }
;
float() { this.ws.float(this.generator.float()); }
;
double() { this.ws.double(this.generator.double()); }
;
bytes() { this.ws.write(this.generator.bytes()); }
;
assert() { this.generator.assert(); }
// ## array()
array(fn) {
let array = this.generator.array();
this.ws.dword(array.length);
for (let i = 0; i < array.length; i++) {
let unknown = array[i].writer(this.ws);
fn.call(unknown, unknown, i);
}
}
// ## repeat()
// Helper for repeating a certain pattern a few times.
repeat(n, fn) {
for (let i = 0; i < n; i++) {
fn(this);
}
}
}