sc4
Version:
A command line utility for automating SimCity 4 modding tasks & modifying savegames
163 lines (162 loc) • 4.11 kB
JavaScript
let val;
export default function parseStringExemplar(str) {
val = str;
// Read the parent cohort.
until('ParentCohort');
ws();
until('=');
ws();
until(':');
ws();
let parent = [readHex(), readHex(), readHex()];
// Next hex we find is the prop count.
const propCount = readHex();
until('\n');
let properties = [];
for (let i = 0; i < propCount; i++) {
properties.push(readProp());
}
return {
parent,
properties,
};
}
function advance(n) {
val = val.slice(n);
}
function until(token) {
let index = val.indexOf(token);
advance(index + token.length);
}
// Consumes whitespace, but only if follows.
const whitespaceRegex = /\s+/;
function ws() {
let match = val.match(whitespaceRegex);
if (!match)
return;
if (match.index === 0) {
advance(match[0].length);
}
}
const hexRegex = /0x[0-9a-f]+/i;
function readHexString() {
let match = val.match(hexRegex);
if (!match)
return undefined;
advance(match.index + match[0].length);
return match[0];
}
function readHex() {
let str = readHexString();
if (str === undefined)
return undefined;
return Number(str);
}
// Reads in a single property line.
function readProp() {
// Work on a per-line basis so that we never surpass a line and read stuff
// from another property by accident.
let index = val.indexOf('\n');
let line = val.slice(0, index);
advance(index);
let temp = val;
val = line;
let id = readHex();
until(':');
// Read a potential comment.
ws();
let comment = readComment();
// Read the values.
until('=');
// Read the type.
index = val.indexOf(':');
let type = val.slice(0, index);
advance(index + 1);
// Read the resp.
index = val.indexOf(':');
let reps = Number(val.slice(0, index));
advance(index + 1);
let value = readValue(type, reps);
// Restore.
val = temp;
// Consume trailing whitespace.
ws();
// Convert the string type to an actual type hint used by the prop.
return { id, comment, type, value };
}
const commentRegex = /^{"(.*)"}/;
function readComment() {
let match = val.match(commentRegex);
if (!match)
return;
return match[1];
}
const stringRegex = /{"(.*)"}/;
function readValue(type, reps) {
if (type === 'String') {
let match = val.match(stringRegex);
if (!match)
return '';
advance(match.index + match[0].length);
return match[1];
}
if (reps === 0) {
return readSingleValue(type);
}
else {
let out = [];
for (let i = 0; i < reps; i++) {
out.push(readSingleValue(type));
}
return out;
}
}
function readSingleValue(type) {
switch (type) {
case 'Uint32':
case 'Uint16':
case 'Uint8':
return readHex();
case 'Float32':
return readFloat();
case 'Sint64':
return readBigInt();
case 'Sint32':
return readInt32();
case 'Bool':
return readBoolean();
default:
throw new Error('Unknown type "' + type + '"!');
}
}
const floatRegex = /([+-]?\d+(\.\d+)?)/;
function readFloat() {
let match = val.match(floatRegex);
if (!match)
return undefined;
advance(match.index + match[0].length);
return Number(match[1]);
}
function readInt32() {
let hex = readHexString();
if (hex === undefined)
return undefined;
return Number(hex);
}
function readBigInt() {
let hex = readHexString();
if (hex === undefined)
return undefined;
if (typeof BigInt === 'undefined') {
throw new Error('Your platform does not support the BigInt primitive!');
}
return BigInt(hex);
}
const boolRegex = /(true|false)/i;
function readBoolean() {
let match = val.match(boolRegex);
if (!match)
return undefined;
advance(match.index + match[0].length);
return String(match[0]).toLowerCase() === 'true';
}