From c3abf2726698d47c064482d4459af85f75c6fd30 Mon Sep 17 00:00:00 2001 From: Columbus Labs Date: Thu, 22 Oct 2026 09:22:09 -0700 Subject: [PATCH] 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> --- packages/lib/src/factorio/exchange_string.ts | 4 ++- test/integration/clusterio.js | 5 ++- test/lib/factorio/exchange_string.js | 20 ++++++++++++ test/lib/factorio/test_settings.js | 8 +++++ test/lib/factorio/test_strings.js | 32 ++++++++++++++++++++ 5 files changed, 67 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/factorio/exchange_string.ts b/packages/lib/src/factorio/exchange_string.ts index 3749fd6a..38d2a36a 100644 --- a/packages/lib/src/factorio/exchange_string.ts +++ b/packages/lib/src/factorio/exchange_string.ts @@ -239,7 +239,9 @@ function readPollution(state: MapReaderState) { pollution_with_max_forest_damage: readOptional(state, readDouble), pollution_per_tree_damage: readOptional(state, readDouble), pollution_restored_per_tree_damage: readOptional(state, readDouble), - max_pollution_to_restore_trees: readOptional(state, readDouble), + ...state.versionAtLeast(2, 1, 18) ? {} : { + max_pollution_to_restore_trees: readOptional(state, readDouble), // removed v2.1.18 + }, enemy_attack_pollution_consumption_modifier: readOptional(state, readDouble), }; } diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index 8508482f..08ee24b7 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -1289,8 +1289,11 @@ describe("Integration of Clusterio", function() { const isV2 = version > lib.integerPartialVersion("2.0"); const isV2_1 = version > lib.integerPartialVersion("2.1"); const isV2_1_13 = version >= lib.integerPartialVersion("2.1.13"); + const isV2_1_18 = version >= lib.integerPartialVersion("2.1.18"); let exchangeString = testStrings.modified; - if (isV2_1_13) { + if (isV2_1_18) { + exchangeString = testStrings.modified_v2_1_18; + } else if (isV2_1_13) { exchangeString = testStrings.modified_v2_1_13; } else if (isV2_1) { exchangeString = testStrings.modified_v2_1; diff --git a/test/lib/factorio/exchange_string.js b/test/lib/factorio/exchange_string.js index 9ea118dc..fda40834 100644 --- a/test/lib/factorio/exchange_string.js +++ b/test/lib/factorio/exchange_string.js @@ -78,6 +78,26 @@ describe("lib/factorio/exchange_string", function() { }); }); + 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("<>"), diff --git a/test/lib/factorio/test_settings.js b/test/lib/factorio/test_settings.js index c1d397f4..b902e90d 100644 --- a/test/lib/factorio/test_settings.js +++ b/test/lib/factorio/test_settings.js @@ -622,6 +622,14 @@ export const modified_v2_1_13_map_settings = { }, }; +export const modified_v2_1_18_map_settings = { + ...modified_v2_1_13_map_settings, + "pollution": Object.fromEntries( + Object.entries(modified_v2_1_13_map_settings.pollution) + .filter(([key]) => key !== "max_pollution_to_restore_trees") + ), +}; + export const modified_space_age_map_gen_settings = { autoplace_controls: { "aquilo_crude_oil": { frequency: 1, size: 1, richness: 1 }, diff --git a/test/lib/factorio/test_strings.js b/test/lib/factorio/test_strings.js index 331993e9..36fa5116 100644 --- a/test/lib/factorio/test_strings.js +++ b/test/lib/factorio/test_strings.js @@ -100,3 +100,35 @@ JYkZpmFakZGd1AkjtxhSQjYziizPKvHmmTPmUDWuYoiVKOgP57RB9yS 0Dvo1NMEfJJWw1i+YLmuywM1C1jwbPKaGnjffEMETl1yE9Qxu/9C7rp u3nVP+RQebGLpzeY4SxwbkUKGDDtjU/J9P3/nrIfNHXdPAWgFF7JHkZ 0L+YLmXPjcc3XoAz1D69BVjk1sULxb83nuUA<<<`; + +export const modified_v2_1_17 = ` +>>>eNp1U89rE0EUftMmNYnVRoiCIjWHHkRICNFTkezoRTx4ELxKnGwm +unS7087uVqsHc/DgQRCkF73o1YpexIO3gBd7k/oPVOrB3lopUkSI8zI +7kzXBB+/x7fd+v2EngMAxUDKdcQXzAbqO0YIrlpa4rAjJld/SeVfGbV +4Rnp9iaYEHfHG10mIhBlOA3sCV86QIdIWerTAdsHjFC5uu73U66Y5ZK +dyFMM2cCCMmIy+43WSSs+ai8MIoxmqppDASQdI0YSLJefhmfd1R2kDm +cCxZ4MWLepIu8jryLou4xD1MJJD3P29udB/NAmr/IZT7fVSFtlQcKpC +ujiaKTCRbdEUQSeHPs/jefMtjYb5Sq9ZrKMfTro7kyzEP3NVcvVr712 +2WG0k/NeYf1qhVzw8kz32+wiJPBDMWNX22wMNscmQoX1J62R6OEPKg9 +O7Kt/trDtErVGkCDhKm1zLM1QQ8e0v/58oYF1wwYNdRTfbubD/+cLDf +IH9e721ea91yyI3vxeXw3E5DOY/gMSfRTFmkc7fMVDk6yijw4jnKjkO +ymFEaphUp2V2bAFK7PgVkM6P44lFlXj1RpnzahjVM0RIlnYH8coi+5L +YBX8cmmKPkIraaRfMZTXbYGajaR4OnlNAzxntyGKLy65CeoY1fepcN0 +/ZTqv/IIHNjF07vMcLY4FwKFLBh25ofk+l7fzlkvuhLOngLwKh9krwM +6F9Ml7LnxuMbL8BZap/eAizycea38xdDLuYt<<<`; + +export const modified_v2_1_18 = ` +>>>eNp1U89rE0EUftMmbRKrBomCIjWHHkRICNFTkezoRTx4EPQocbKZ +6NLNTju7W60ezMGDB0GQXvSiVyvqQQSPBS/2JvUfqNSDvbVSpAchzsv +sTNZUH7zHt9/7/SYZAwJHIAMwlXEF8wF6jtGCK+bnuawIyQHA0nlXxm +1eEZ6fYmmBB7y7VGmxEIMpwOrAlfOkCHSFVVthKmDxohc2Xd/rdNIds +1K4c2GaORZGTEZecKvJJGfNrvDCKMZqqaQwEkHSNGEiyXn4emXFUdpA +5kAsWeDFXT1JD3kdeYdFXOIeJhLI+5831noPpwG1/wDK/T6qQhsqDhV +IT0cTRSaSLboiiKTwZ1l8d7blsTBfqVXrNZSjaVdH8oWYB+5Srl6t/e +02y42kn9jnH9aoVc8OJM99vsgiTwSHLWr6bI6H2eTIUL6g9KI9HCHkf +untpW/3lh2iV6jSBOwlzGrLMJcT8PQN/Z8rY1yw7ajaO7c3H33Y222Q +36921q+0bjrk2vfiQnhmq6GcB/GG42gmLNK5G2aYHB1lFHj+DGXLIVn +MKA3TipRsL48BqV2dALKufsykeEiZl4+VKZ+0YQ1TtERJZyC/HKIPuG +nA130TzFByHltNo/mMJjvsDFTto8ETSugp4z0+DFH5dUjP0MYvvcuaa +fsp1X9kkBkzyDn6jz1GGBucS4ECNmxb82M8fe8vk+aLvqCDtwCM2iXJ +y4D+Z+lS9tx4fOMFOE3t01uARSY/vrv+B8BC5X8=<<<`;