local lfs = require("lfs") local enableDebug = true local enableImGui = false local flatpakSteam = true -- Name of your local workshop container local workshopName = "MyModWorkshop" -- Mods live here inside the git repo local repoModsDir = "mods" local isWindows = package.config:sub(1, 1) == "\\" local sep = isWindows and "\\" or "/" local homeDir = isWindows and os.getenv("userprofile") or os.getenv("HOME") local flatpakSteamVar = not isWindows and os.getenv("HOME") .. "/.var/app/com.valvesoftware.Steam" -- Launch via Steam local pzCacheRoot local pzExecutable if isWindows then pzCacheRoot = (homeDir .. "\\Zomboid") pzExecutable = "steam.exe -applaunch 108600" else pzExecutable = flatpakSteam and "flatpak run com.valvesoftware.Steam -applaunch 108600" or "steam -applaunch 108600" pzCacheRoot = flatpakSteam and (flatpakSteamVar .. "/Zomboid") or (homeDir .. "/Zomboid") end -- Final destination: Workshop//Contents/mods local pzModsDir = pzCacheRoot .. sep .. "Workshop" .. sep .. workshopName .. sep .. "Contents" .. sep .. "mods" -- ========================= -- File helpers -- ========================= local function copyFile(src, dst) local inFile = assert(io.open(src, "rb")) local data = inFile:read("*all") inFile:close() local outFile = assert(io.open(dst, "wb")) outFile:write(data) outFile:close() end local function mkdir(path) if isWindows then os.execute(string.format('mkdir "%s" 2>nul', path)) else os.execute(string.format('mkdir -p "%s"', path)) end end local function copyDir(src, dst) mkdir(dst) for name in lfs.dir(src) do if name ~= "." and name ~= ".." then local srcPath = src .. sep .. name local dstPath = dst .. sep .. name local attr = lfs.attributes(srcPath) if attr.mode == "directory" then copyDir(srcPath, dstPath) else copyFile(srcPath, dstPath) end end end end -- ========================= -- Safety helpers -- ========================= local function normalizePath(path) path = path:gsub("\\", "/") return path:gsub("/+$", "") end local function isRootPath(path) if path == "/" then return true end if path:match("^[A-Za-z]:$") then return true end return false end local function isSubPath(child, parent) child = normalizePath(child) parent = normalizePath(parent) return child:sub(1, #parent + 1) == parent .. "/" end local function validatePath(target, root, home) local t = normalizePath(target) local r = normalizePath(root) local h = normalizePath(home) assert(t ~= "", "Refusing to delete empty path") assert(not isRootPath(t), "Refusing to delete root path") assert(t ~= h, "Refusing to delete home directory") assert(isSubPath(t, r), "Refusing to delete outside workshop mods") assert(#t > #r + 1, "Refusing to delete mods root") end -- ========================= -- Discover mods dynamically -- ========================= local mods = {} for name in lfs.dir(repoModsDir) do if name ~= "." and name ~= ".." then local path = repoModsDir .. sep .. name local attr = lfs.attributes(path) if attr and attr.mode == "directory" then table.insert(mods, name) end end end assert(#mods > 0, "No mods found in ./mods directory") -- ========================= -- Copy mods -- ========================= print("Installing mods into workshop:") mkdir(pzModsDir) for _, modName in ipairs(mods) do local src = repoModsDir .. sep .. modName local dst = pzModsDir .. sep .. modName print(" - " .. modName) print(" - copied" .. src .. " to " .. dst) validatePath(dst, pzModsDir, homeDir) copyDir(src, dst) end print("All mods copied successfully!") -- ========================= -- Launch Project Zomboid -- ========================= local params = {} table.insert(params, "-modfolders workshop,mods") if enableDebug then table.insert(params, "-debug") end if enableImGui then table.insert(params, "-imgui") end local cmd = string.format("%s %s", pzExecutable, table.concat(params, " ")) print("Launching:") print(cmd) os.execute(cmd)