sc4
Version:
A command line utility for automating SimCity 4 modding tasks & modifying savegames
94 lines (93 loc) • 3.77 kB
JavaScript
// # build-family-index.ts
import PQueue from 'p-queue';
import { FileType, TGI } from 'sc4/core';
import { indexOf } from 'uint8array-extras';
// The property id 0x27812870 as 32-bit LE, as well as in text format.
const binarySequence = new Uint8Array([0x70, 0x28, 0x81, 0x27]);
const textSequence = new Uint8Array([48, 120, 50, 55, 56, 49, 50, 56, 55, 48]);
// # buildFamilyIndex(index)
// Builds up the index of all building & prop families, maximized for speed.
export default async function buildFamilyIndex(index, opts = {}) {
const families = new Map();
const cache = new Map();
const { concurrency = 4096 } = opts;
const exemplars = index.findAll({ type: FileType.Exemplar });
const queue = new PQueue({ concurrency });
const tasks = new Array(exemplars.length);
for (let entry of exemplars) {
// If the entry has group id, then we can tell it's a lot configurations
// exemplar, so no need to parse it in that casae.
if (entry.group === 0xa8fbd372)
continue;
const task = queue.add(async () => {
const propFamilies = await getFamilies(index, cache, entry);
if (!propFamilies || propFamilies.length === 0)
return;
for (let family of propFamilies) {
const tgis = families.get(family);
if (tgis) {
tgis.push(entry.tgi);
}
else {
families.set(family, [entry.tgi]);
}
}
});
tasks.push(task);
}
await Promise.all(tasks);
// We're not done yet. If a prop pack adds props to a Maxis family, then
// multiple of the *same* tgi might be present in the family array. We
// have to avoid this, so we need to filter the tgi's again to be unique.
for (let [family, tgis] of families) {
let had = new Set();
let filtered = tgis.filter(tgi => {
let id = tgi.toBigInt();
if (!had.has(id)) {
had.add(id);
return true;
}
else {
return false;
}
});
families.set(family, filtered);
}
return families;
}
async function getFamilies(index, cache, entry) {
// IMPORTANT! We won't just blindly read in the exemplar, because
// that requires us to parse all exemplars, also the non-prop
// exemplars. No, instead we will read in the raw decompressed
// buffer, and then we'll look whether the 0x27812870 byte sequence
// (in reverse order because of Little Endian) is present. If not,
// this file does not need to be parsed.
const buffer = await entry.decompressAsync();
const seq = buffer[3] === 0x54 ? textSequence : binarySequence;
const patternIndex = indexOf(buffer, seq);
// If the index was found, cool, it's very likely that the prop
// belongs to a family. We will parse the exemplar now.
if (patternIndex > -1) {
const exemplar = await entry.readAsync();
const families = exemplar.get(0x27812870);
if (families)
return families;
}
// If we didn't find any families by now, we'll check if there's a parent
// cohort.
const dv = new DataView(buffer.buffer, buffer.byteOffset);
const type = dv.getUint32(8, true);
if (type === 0)
return;
const group = dv.getUint32(8, true);
const instance = dv.getUint32(8, true);
const parent = index.find({ type, group, instance });
if (!parent)
return;
let promise = cache.get(parent);
if (promise)
return await promise;
promise = getFamilies(index, cache, parent);
cache.set(parent, promise);
return await promise;
}