sc4
Version:
A command line utility for automating SimCity 4 modding tasks & modifying savegames
271 lines (270 loc) • 7.94 kB
JavaScript
// # network-index.js
import Stream from './stream.js';
import WriteBuffer from './write-buffer.js';
import Unknown from './unknown.js';
import { FileType } from './enums.js';
import { kFileType } from './symbols.js';
// # NetworkIndex
export class NetworkIndex {
static [kFileType] = FileType.NetworkIndex;
mem = 0x00000000;
crc = 0x00000000;
major = 0x0007;
cityTiles = 4096;
tiles = [];
intersections = [];
transitEnabledTiles = [];
tileX = 0x00000000;
tileZ = 0x00000000;
yIntersections = [];
u = new Unknown()
.dword(0x00000000)
.dword(0x00000000)
.bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
// ## parse(buffer)
parse(buffer) {
let rs = new Stream(buffer);
const u = this.u.reader(rs);
rs.size();
this.crc = rs.dword();
this.mem = rs.dword();
this.major = rs.word();
this.cityTiles = rs.dword();
let citySize = this.cityTiles ** 0.5;
this.tiles = rs.array(() => {
let tile = new NetworkIndexTile();
tile.parse(rs, { citySize });
return tile;
});
this.intersections = rs.array(() => rs.struct(NetworkIntersection));
this.transitEnabledTiles = rs.array(() => {
return rs.struct(TransitEnabledTile);
});
// Don't know what happens below lol.
u.dword();
u.dword();
this.tileX = rs.dword();
this.tileZ = rs.dword();
u.bytes(10);
// Next another array of pointers follows, either of type 0xc9c05c6e
// (NetworkOccupant), or 0x49c1a034 (
// NetworkOccupantWithPrebuiltModel). We don't really understand what
// those represent, but they have something to do with complex Y and Ψ
// intersections.
if (this.major !== 0x003) {
this.yIntersections = rs.array(() => {
return rs.struct(ComplexIntersection);
});
}
rs.assert();
return;
}
// ## toBuffer()
toBuffer() {
let ws = new WriteBuffer();
const unknown = this.u.writer(ws);
ws.dword(this.mem);
ws.word(this.major);
ws.dword(this.cityTiles);
let citySize = this.cityTiles ** 0.5;
ws.array(this.tiles, tile => {
tile.write(ws, { citySize });
});
ws.array(this.intersections);
ws.array(this.transitEnabledTiles);
unknown.dword();
unknown.dword();
ws.dword(this.tileX);
ws.dword(this.tileZ);
unknown.bytes();
ws.array(this.yIntersections);
return ws.seal();
}
// ## tile(opts)
// Helper function for creating a new tile. Note that it doesn't insert
// it, you have to do this manually!
tile() {
return new NetworkIndexTile();
}
}
export default NetworkIndex;
export class NetworkIndexTile {
x = 0;
z = 0;
pointer;
blocks;
automata;
reps = [
new Uint8Array(12),
new Uint8Array(12),
new Uint8Array(12),
new Uint8Array(12),
];
reps2 = [];
u = new Unknown()
.byte(0x00)
.dword(0x00000000)
.dword(0x00000000)
.dword(0x00000000)
.word(0x0000);
// ## parse(rs)
parse(rs, { citySize }) {
let u = this.u.reader(rs);
let nr = rs.dword();
this.x = nr % citySize;
this.z = Math.floor(nr / citySize);
this.pointer = rs.pointer();
this.blocks = rs.array(() => {
let nr = rs.dword();
return {
nr,
bytes: rs.array(() => rs.read(8)),
};
});
this.automata = rs.array(() => rs.pointer());
u.byte();
u.dword();
u.dword();
u.dword();
this.reps = [];
for (let i = 0; i < 4; i++) {
this.reps.push(rs.read(12));
}
u.word();
// Next follows an array where each record counts 10 bytes.
this.reps2 = rs.array(() => {
return {
nr: rs.dword(),
bytes: rs.read(6),
};
});
}
// ## write(ws)
write(ws, { citySize }) {
let u = this.u.writer(ws);
let nr = this.z * citySize + this.x;
ws.dword(nr);
ws.pointer(this.pointer);
ws.array(this.blocks, block => {
ws.dword(block.nr);
ws.array(block.bytes, bytes => ws.write(bytes));
});
ws.array(this.automata, ptr => ws.pointer(ptr));
u.byte();
u.dword();
u.dword();
u.dword();
for (let rep of this.reps) {
ws.write(rep);
}
u.word();
ws.array(this.reps2, item => {
ws.dword(item.nr);
ws.write(item.bytes);
});
}
}
// # NetworkIntersection
// The class for respenting an intersection on the network. Note that those
// tiles already appear as well in the normal tiles array! This is just an
// additional structure somehow.
class NetworkIntersection {
pointer = null;
west = null;
north = null;
east = null;
south = null;
u = new Unknown()
.byte(0x00)
.dword(0x00000000);
// ## parse(rs)
parse(rs) {
const u = this.u.reader(rs);
this.pointer = rs.pointer();
u.byte();
u.dword();
for (let dir of ['west', 'north', 'east', 'south']) {
// Next the game does something strange. We have to read in a
// count, but it does not specify the size of an array, instead it
// specifies how many *meaningful* blocks there are to follow!
// Nevertheless we still read in everything because we want to
// check if reserializing happens correctly! Just know that you
// can discard the values!
let count = rs.dword();
let bool = rs.bool();
let array = [];
for (let i = 0; i < 3; i++) {
let bytes = rs.read(4);
array.push({
bytes,
ignore: i >= count,
});
}
this[dir] = { bool, array };
}
}
// ## write(ws)
write(ws) {
let u = this.u.writer(ws);
ws.pointer(this.pointer);
u.byte();
u.dword();
for (let dir of ['west', 'north', 'east', 'south']) {
let { array, bool } = this[dir];
let { length } = array.filter(({ ignore }) => !ignore);
ws.dword(length);
ws.bool(bool);
for (let { bytes } of array) {
ws.write(bytes);
}
}
}
}
// # TransitEnabledTile
// The class for representing a transit enabled tile in the network index.
class TransitEnabledTile {
x = 0x0000;
z = 0x0000;
dword = 0x00000000;
pointer = null;
// ## parse(rs)
parse(rs) {
this.z = rs.word();
this.x = rs.word();
this.dword = rs.dword();
this.pointer = rs.pointer();
}
// ## write(ws)
write(ws) {
ws.word(this.z);
ws.word(this.x);
ws.dword(this.dword);
ws.pointer(this.pointer);
}
}
class ComplexIntersection {
pointer = null;
west = null;
north = null;
east = null;
south = null;
// ## parse(rs)
parse(rs) {
this.pointer = rs.pointer();
for (let dir of ['west', 'north', 'east', 'south']) {
this[dir] = {
array: rs.array(() => rs.dword()),
word: rs.word(),
};
}
}
// ## write(ws)
write(ws) {
ws.pointer(this.pointer);
for (let dir of ['west', 'north', 'east', 'south']) {
let struct = this[dir];
ws.array(struct.array, dword => ws.dword(dword));
ws.word(struct.word);
}
}
}