const { readdirSync: readDir, readFileSync } = require("fs"); const { readFile, writeFile, mkdir } = require("fs/promises"); const homeDir = require("os").homedir(); const { join: joinPath, relative: relativePath, sep: pathSeparator } = require("path"); const { LuaFactory } = require('wasmoon'); const FACTORIO_DATA_DIR = process.env["FACTORIO_DATA_DIR"] ?? `${homeDir}/.steam/steam/steamapps/common/Factorio/data`; const LOCALE = process.env["LOCALE"] ?? "en"; const MODS = "core;base" + (process.env["MODS"] ?? ((process.env["SPACE_AGE"] ?? "true") == "true" ? ";quality;elevated-rails;space-age" : "")); const mods = MODS.split(";").filter(v => v); const readLocaleData = mod => readFileSync(joinPath(FACTORIO_DATA_DIR, mod, "locale", LOCALE, mod + ".cfg"), "utf-8") .trim() .split("\n") .filter(v => v) .map(v => v.split(/=(.*)/, 2)) .flatMap((category = null, ([ k, v ]) => k.startsWith("[") ? (category = k, []) : { key: k, value: v, category })) .reduce((m, { key, value, category }) => ((m.get(category) ?? m.set(category, new Map()).get(category)).set(key, value), m), new Map()); const defines = [...readFileSync(joinPath(FACTORIO_DATA_DIR, "..", "doc-html", "defines.html"), "utf8") .matchAll(/id="(defines\..*?)"/g)] .map(v => v[1]) .flatMap((v, i) => i === 0 ? ["defines", v] : v) // prepend 'defines' .map(v => [...v.matchAll(/([a-zA-Z0-9_]+)|\['(.+?)'\]/g)].map(([ , a, b ]) => a ?? b)) .reduce((acc, keys, idx) => { let v = acc; let lastKey = keys.pop(); keys.forEach(k => (typeof v[k] != "object" ? v[k] = {} : null, v = v[k])); v[lastKey] = idx; return acc; }, {}) .defines; const loadModFactory = async () => { const luaFactory = new LuaFactory(); const lua = await luaFactory.createEngine({ enableProxy: false }); await luaFactory.mountFile("serpent.lua", await readFile(joinPath("serpent", "src", "serpent.lua"))); await lua.doString("serpent = require('serpent')"); await Promise.all(readDir(joinPath(FACTORIO_DATA_DIR), { recursive: true, withFileTypes: true }) .filter(v => v.isFile() && v.name.endsWith(".lua")) .map(async file => { const data = await readFile(joinPath(file.parentPath, file.name)); if (file.parentPath.endsWith("lualib")) { await luaFactory.mountFile(file.name, data); } const rPath = relativePath(joinPath(FACTORIO_DATA_DIR), joinPath(file.parentPath, file.name)) .split(pathSeparator); const luaModule = rPath.shift(); // console.log(joinPath(`__${luaModule}__`, ...rPath)) await luaFactory.mountFile(joinPath(`__${luaModule}__`, ...rPath), data); })); lua.global.set("defines", defines); await lua.doString("math.atan2 = function(x, y) return math.atan(y / x) end"); await lua.doString("require('dataloader')"); const execStage = async (mod, stage) => { const files = readDir(joinPath(FACTORIO_DATA_DIR, mod), { recursive: true, withFileTypes: true }); if (!files.some(v => v.name === `${stage}.lua`)) { // skip return; } await lua.doString(files .filter(v => v.isFile() && v.name.endsWith(".lua") && !v.parentPath.endsWith("lualib") && v.parentPath.indexOf("prototype") >= 0) .map(file => relativePath(joinPath(FACTORIO_DATA_DIR, mod), joinPath(file.parentPath, file.name)).replace(/\.lua$/, "")) .flatMap(file => [ file, file.replaceAll(pathSeparator, '.') ]) .reduce((s, p) => s + `package.loaded['${p}'] = nil\n`, "")); await Promise.all(files .filter(v => v.isFile() && v.name.endsWith(".lua") && !v.parentPath.endsWith("lualib")) .map(async file => { const moduleName = relativePath(joinPath(FACTORIO_DATA_DIR, mod), joinPath(file.parentPath, file.name)) return luaFactory.mountFile(moduleName, (mod === "space-age" && moduleName === "prototypes/ambient-sounds.lua" ? "require('__space-age__/sound/ambient/space/interlude-6/interlude-6')" : "") + // no clue why this fix is needed, it does nothing functional await readFile(joinPath(file.parentPath, file.name)), ) })); console.log(`${stage} stage,\t${mod}`); await lua.doFile(`${stage}.lua`); return lua.global.get("data"); } return async mods => { for (const mod of mods) { await execStage(mod, "data"); } for (const mod of mods) { await execStage(mod, "data-updates"); } for (const mod of mods) { await execStage(mod, "data-final-fixes"); } return lua.global.get("data"); } } console.log("loading mods:", mods); loadModFactory().then(async loadMods => { const data = await loadMods(mods); await mkdir("build", { recursive: true }); await writeFile(joinPath("build", "data.json"), JSON.stringify(data.raw)); });