UNPKG

sc4

Version:

A command line utility for automating SimCity 4 modding tasks & modifying savegames

163 lines (162 loc) 5.27 kB
// # 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); } } }