From 53a5d7ea6ff1ae14e1ac861bdd3df32ac5790b2e Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Tue, 11 Oct 2022 13:17:01 +0200 Subject: [PATCH] Add ModPack.toModSettingsDat Add utility function to convert the settings representation of a ModPack into the mod-settings.dat format which Factorio reads. --- packages/lib/data/ModPack.js | 97 ++++++++++++++++++++++++++++++++++++ test/lib/data/ModPack.js | 70 ++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/packages/lib/data/ModPack.js b/packages/lib/data/ModPack.js index 52c99d6a..0b58a0f2 100644 --- a/packages/lib/data/ModPack.js +++ b/packages/lib/data/ModPack.js @@ -208,6 +208,103 @@ class ModPack { return new this(json); } + + toModSettingsDat() { + function uint8Byte(int) { + return Buffer.from(Uint8Array.from([int]).buffer); + } + function int16Bytes(int) { + return Buffer.from(Int16Array.from([int]).buffer); + } + function uint32Bytes(int) { + return Buffer.from(Uint32Array.from([int]).buffer); + } + function uint32SpaceOptimizedBytes(int) { + if (0 <= int && int < 0xff) { + return uint8Byte(int); + } + return Buffer.concat([ + uint8Byte(0xff), + uint32Bytes(int), + ]); + } + function doubleBytes(double) { + return Buffer.from(Float64Array.from([double]).buffer); + } + function stringBytes(str) { + const buf = Buffer.from(str, "utf8"); + return Buffer.concat([ + uint32SpaceOptimizedBytes(buf.length), + buf, + ]); + } + function immutableStringBytes(str) { + if (str === undefined) { + return uint8Byte(1); // empty + } + return Buffer.concat([ + uint8Byte(0), // empty + stringBytes(str), + ]); + } + function versionBytes(version) { + const [main, major, minor] = version.split(".").map(n => Number.parseInt(n, 10)); + return Buffer.concat([ + int16Bytes(main), + int16Bytes(major), + int16Bytes(minor), + Buffer.alloc(2), // developer + Buffer.alloc(1), // reserved byte + ]); + } + function propertyTreeListBytes(entries) { + const sizeBytes = uint32Bytes(entries.length); + const itemBytes = entries.flatMap(([key, item]) => [ + immutableStringBytes(key), + // eslint-disable-next-line no-use-before-define + propertyTreeBytes(item), + ]); + return Buffer.concat([ + sizeBytes, + ...itemBytes, + ]); + } + function propertyTreeBytes(element) { + let type; + let dataBytes; + if (typeof element === null) { + type = 1; + dataBytes = Buffer.alloc(0); + } else if (typeof element === "boolean") { + type = 1; + dataBytes = uint8Byte(element); + } else if (typeof element === "number") { + type = 2; + dataBytes = doubleBytes(element); + } else if (typeof element === "string") { + type = 3; + dataBytes = immutableStringBytes(element); + } else if (element instanceof Array) { + type = 4; + dataBytes = propertyTreeListBytes(element.map(item => [undefined, item])); + } else if (typeof element === "object") { + type = 5; + dataBytes = propertyTreeListBytes(Object.entries(element)); + } else { + throw new Error("Bad element passed to propertyTreeBytes"); + } + return Buffer.concat([ + uint8Byte(type), + Buffer.alloc(1), // anyTypeFlag + dataBytes, + ]); + } + + return Buffer.concat([ + versionBytes(this.factorioVersion), + propertyTreeBytes(this.toJSON().settings), + ]); + } } module.exports = ModPack; diff --git a/test/lib/data/ModPack.js b/test/lib/data/ModPack.js index c26540dd..8a6fed3e 100644 --- a/test/lib/data/ModPack.js +++ b/test/lib/data/ModPack.js @@ -90,5 +90,75 @@ describe("lib/data/ModPack", function() { ); }); }); + + describe("toModSettingsDat()", function() { + it("should properly serialise settings", function() { + const pack = new ModPack({ settings: { + "startup": { + "bool-setting": { "value": true }, + }, + "runtime-global": { + "number-setting": { "value": 123 }, + }, + "runtime-per-user": { + "string-setting": { "value": "a string" }, + }, + }}); + + function istr(str) { + return [ + Uint8Array.from([0, str.length]), + Buffer.from(str), + ]; + } + /* eslint-disable indent */ + assert.deepEqual( + pack.toModSettingsDat(), + Buffer.concat([ + new Uint8Array(Uint16Array.from([1, 1, 0, 0]).buffer), // version + Uint8Array.from([0]), // reserved + + Uint8Array.from([5, 0]), // dictionary + new Uint8Array(Uint32Array.from([3]).buffer), // items + + ...istr("startup"), // name + Uint8Array.from([5, 0]), // dictionary + new Uint8Array(Uint32Array.from([1]).buffer), // items + + ...istr("bool-setting"), // name + Uint8Array.from([5, 0]), // dictinary + new Uint8Array(Uint32Array.from([1]).buffer), // items + + ...istr("value"), // name + Uint8Array.from([1, 0, 1]), // boolean + + ...istr("runtime-global"), // name + Uint8Array.from([5, 0]), // dictionary + new Uint8Array(Uint32Array.from([1]).buffer), // items + + ...istr("number-setting"), // name + Uint8Array.from([5, 0]), // dictinary + new Uint8Array(Uint32Array.from([1]).buffer), // items + + ...istr("value"), // name + Uint8Array.from([2, 0]), // number + new Uint8Array(Float64Array.from([123]).buffer), + + ...istr("runtime-per-user"), // name + Uint8Array.from([5, 0]), // dictionary + new Uint8Array(Uint32Array.from([1]).buffer), // items + + ...istr("string-setting"), // name + Uint8Array.from([5, 0]), // dictinary + new Uint8Array(Uint32Array.from([1]).buffer), // items + + ...istr("value"), // name + Uint8Array.from([3, 0, 0, "a string".length]), // string + Buffer.from("a string"), + ]) + ); + /* eslint-enable indent */ + }); + }); }); });