sc4
Version:
A command line utility for automating SimCity 4 modding tasks & modifying savegames
167 lines (166 loc) • 5.53 kB
JavaScript
// # 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();
}
}