Files
c3abf27266 Skip max_pollution_to_restore_trees in 2.1.18+ exchange strings (#1132)
Factorio 2.1.18 removed PollutionSettings::max_pollution_to_restore_trees,
but readPollution still read it for every version, so every map setting
after it was read 9 bytes late: enemy_attack_pollution_consumption_modifier
came out as garbage, the enemy_evolution factors shifted by one field and
enemy_expansion.enabled and both expansion distances came out null.

Read the field only for strings older than 2.1.18. Add modified_v2_1_17
and modified_v2_1_18 fixtures generated with headless 2.1.17 and 2.1.18
from the modified 2.1 settings (base game only), unit tests asserting
their full decoded output, and use the 2.1.18 string for the
load-scenario integration test on 2.1.18 and later.

Fixes #1131

Co-authored-by: ColumbusLabs <287001685+ColumbusLabs@users.noreply.github.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
2026-10-22 17:22:09 +01:00

142 lines
4.4 KiB
JavaScript

import assert from "node:assert/strict";
import zlib from "node:zlib";
import * as lib from "@clusterio/lib";
import * as testStrings from "./test_strings.js";
import * as testSettings from "./test_settings.js";
// Simple linear congruential random number generator
class Random {
constructor(seed) {
this.prev = seed;
}
next() {
let curr = (1103515245 * this.prev + 12345) & 0x7fffffff;
this.prev = curr;
return curr >> 16;
}
}
describe("lib/factorio/exchange_string", function() {
describe("readMapExchangeString", function() {
it("should parse post v0.16 strings", function() {
assert.deepEqual(lib.readMapExchangeString(testStrings.default), {
version: [1, 1, 33, 0],
unknown: 0,
map_gen_settings: testSettings.default_map_gen_settings,
map_settings: testSettings.default_map_settings,
checksum: 3169422966,
});
assert.deepEqual(lib.readMapExchangeString(testStrings.modified), {
version: [1, 1, 33, 0],
unknown: 0,
map_gen_settings: testSettings.modified_map_gen_settings,
map_settings: testSettings.modified_map_settings,
checksum: 4092204126,
});
});
it("should parse post v2.0 strings", function() {
assert.deepEqual(lib.readMapExchangeString(testStrings.modified_v2), {
version: [2, 0, 47, 0],
unknown: 0,
map_gen_settings: testSettings.modified_v2_map_gen_settings,
map_settings: testSettings.modified_v2_map_settings,
checksum: 979461261,
});
assert.deepEqual(lib.readMapExchangeString(testStrings.modified_space_age), {
version: [2, 0, 47, 0],
unknown: 0,
map_gen_settings: testSettings.modified_space_age_map_gen_settings,
map_settings: testSettings.modified_v2_map_settings,
checksum: 1746300027,
});
});
it("should parse post v2.1 strings", function() {
assert.deepEqual(lib.readMapExchangeString(testStrings.modified_v2_1), {
version: [2, 1, 9, 3],
unknown: 0,
map_gen_settings: testSettings.modified_v2_1_map_gen_settings,
map_settings: testSettings.modified_v2_1_map_settings,
checksum: 3180296799,
});
});
it("should parse post v2.1.13 strings", function() {
assert.deepEqual(lib.readMapExchangeString(testStrings.modified_v2_1_13), {
version: [2, 1, 14, 1],
unknown: 0,
map_gen_settings: testSettings.modified_v2_1_map_gen_settings,
map_settings: testSettings.modified_v2_1_13_map_settings,
checksum: 272054359,
});
});
it("should parse v2.1.17 strings", function() {
assert.deepEqual(lib.readMapExchangeString(testStrings.modified_v2_1_17), {
version: [2, 1, 17, 0],
unknown: 0,
map_gen_settings: testSettings.modified_v2_1_map_gen_settings,
map_settings: testSettings.modified_v2_1_13_map_settings,
checksum: 1073352633,
});
});
it("should parse post v2.1.18 strings", function() {
assert.deepEqual(lib.readMapExchangeString(testStrings.modified_v2_1_18), {
version: [2, 1, 18, 4],
unknown: 0,
map_gen_settings: testSettings.modified_v2_1_map_gen_settings,
map_settings: testSettings.modified_v2_1_18_map_settings,
checksum: 1437579271,
});
});
it("should handle malformed strings", function() {
assert.throws(
() => lib.readMapExchangeString("<<blah>>"),
new Error("Not a map exchange string")
);
assert.throws(
() => lib.readMapExchangeString(testStrings.default.slice(0, 100)),
new Error("Not a map exchange string")
);
assert.throws(
() => lib.readMapExchangeString(`>>>${Buffer.from("abk430ia404ah3b4").toString("base64")}<<<`),
new Error("Malformed map exchange string: zlib inflate failed")
);
for (let i = 0; i < 100; i++) {
let gen = new Random(i);
let size = gen.next() % 200 + 100;
let data = Buffer.alloc(size);
for (let j = 0; j < size; j++) {
data[j] = gen.next() % 256;
}
// eslint-disable-next-line node/no-sync
data = zlib.deflateSync(data);
assert.throws(
() => lib.readMapExchangeString(`>>>${data.toString("base64")}<<<`),
new Error("Malformed map exchange string: reached end before finishing parsing")
);
}
let pastEnd = Buffer.from(testStrings.default.replace(/><\n/g, ""), "base64");
// eslint-disable-next-line node/no-sync
pastEnd = zlib.deflateSync(Buffer.concat([zlib.inflateSync(pastEnd), Buffer.from("junk")]));
assert.throws(
() => lib.readMapExchangeString(`>>>${pastEnd.toString("base64")}<<<`),
new Error("Malformed map exchange string: data after end")
);
});
});
});