UNPKG

sc4

Version:

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

167 lines (166 loc) 5.53 kB
// # write-buffer.ts import { SmartBuffer } from 'smart-arraybuffer'; import xcrc from './crc.js'; import TGI from './tgi.js'; // # WriteBuffer() // The WriteBuffer class replaces the WriteStream class by making not // requiring us to build up a buffer of unknown size manually. It uses the // smart-buffer module under the hood, but renames a few methods so that it's // easier to work with. Under the hood we'll also provide support for // "formatting" the buffer, i.e. adding the size and the checksum in front of // it automatically, which is what we often need. const encoder = new TextEncoder(); export default class WriteBuffer extends SmartBuffer { // ## write(buffer, ) // The write method is used for writing a raw buffer. write(buffer, offset) { // If an object was passed instead of a raw buffer, we'll check if // either: // - the object supports writing to the buffer. // - the object has a `toBuffer()` method. if (!(buffer instanceof Uint8Array)) { if ('write' in buffer) { buffer.write(this); return; } else { buffer = buffer.toBuffer(); } } super.writeBuffer(buffer, offset); } // ## string(str) // Writing a string will first write the string length as a uint32 and // then the string itself. string(str, opts = {}) { let { length = true } = opts; let buffer = encoder.encode(str); if (length) { this.writeUInt32LE(buffer.byteLength); } this.write(buffer); } // Simple aliases. int8(value) { this.writeInt8(value); } int16(value) { this.writeInt16LE(value); } int32(value) { this.writeInt32LE(value); } bigint64(value) { this.writeBigInt64LE(BigInt(value)); } float(value) { this.writeFloatLE(value); } double(value) { this.writeDoubleLE(value); } uint8(value) { this.writeUInt8(value); } byte(value) { this.writeUInt8(value); } bool(value) { this.writeUInt8(Number(value)); } uint16(value) { this.writeUInt16LE(value); } word(value) { this.writeUInt16LE(value); } uint32(value) { this.writeUInt32LE(value); } dword(value) { this.writeUInt32LE(value); } qword(value) { this.writeBigUInt64LE(value); } // ## n() // Fills the buffer with the given amount of zeroes. zeroes(n) { for (let i = 0; i < n; i++) this.uint8(0); } array(arr, fn = (item => this.write(item))) { this.uint32(arr.length); for (let item of arr) { fn.call(this, item); } } tuple(arr, fn = (item => this.write(item))) { for (let item of arr) { fn.call(this, item); } } // ## tgi() // Writes away a TGI. tgi(tgi) { let [type, group, instance] = new TGI(tgi); this.dword(type); this.dword(group); this.dword(instance); } // ## gti() // Writes away a TGI to the buffer, but in gti form. That's because for some // reason, TGI's are often stored as GTI in savegames. gti(tgi) { let [type, group, instance] = new TGI(tgi); this.dword(group); this.dword(type); this.dword(instance); } // ## date(date) // Writes away a date again as julian date. date(date) { this.dword(date.toJulian()); } // ## version(version) // Writes away a version string. version(version) { let parts = version.split('.'); for (let part of parts) { this.word(+part); } } // ## pointer(ptr) // Writes a pointer data structure to the buffer. If the pointer is // nullish, we write away 0x00000000, i.e. a null pointer. pointer(ptr) { if (!ptr) { this.dword(0x00000000); return; } let { address = ptr.mem } = ptr; this.dword(address); if (address > 0) { this.dword(ptr.type); } } // ## vector3() // Writes away a 3D vector. vector3(vector) { this.float(vector[0]); this.float(vector[1]); this.float(vector[2]); } // ## color(color) // Writes a color data structure to the buffer. color(color) { this.byte(color.r); this.byte(color.g); this.byte(color.b); this.byte(color.a); } // ## vertex(vertex) // Writes a vertex data structure to the buffer. vertex(vertex) { vertex.write(this); } // ## tract(tractInfo) // Writes a tract info data structure to the buffer. tract(tract) { tract.write(this); } // ## bbox(bbox) // Writes a bbox data structure to the buffer. bbox(bbox, opts) { bbox.write(this, opts); } // ## seal() // This method returns a new buffer where the checksum and size have been // prepended. We use this all the time. seal() { // First of all we'll calculate the CRC checksum for the buffer. let buffer = this.toUint8Array(); let sum = xcrc(buffer); // Next we'll prefix it with the size and calculated checksum. this.insertUInt32LE(buffer.length + 8, 0); this.insertUInt32LE(sum, 4); return this.toUint8Array(); } // ## toBuffer() toBuffer() { console.warn('`.toBuffer()` only works in Node.js environments and should be avoided. Use `.toUint8Array()` instead!'); return super.toBuffer(); } }