From e374ae3872f6871e400034be7da8ecad5eed2a8a Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Sun, 26 Jul 2026 11:34:25 +0200 Subject: [PATCH 1/2] fix: update PoE1 bundle runtime Lua handling Bump the bundled PoE1 tree to v2.66.2 so sha2.lua is present in newly packaged apps. Rewrite runtime/lua manifest entries to a macOS-only updater part under App Support and search that Lua path before the bundled fallback. --- .github/workflows/build-macos.yml | 2 +- .github/workflows/release-macos.yml | 2 +- CMakeLists.txt | 2 +- MACOS_PORT.md | 7 ++- PathOfBuilding | 2 +- macos/launcher.cpp | 9 +-- macos/mac_entry.lua | 89 +++++++++++++++++++++-------- 7 files changed, 78 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 085083e..ba50db2 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -19,7 +19,7 @@ jobs: include: - name: Path of Building lua_tree: PathOfBuilding - version: "2.63.0" + version: "2.66.2" bundle_name: "Path of Building" bundle_id: ch.spidy.PathOfBuildingMac app_support_dir: PathOfBuildingMac diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 6c14aa8..0dee31e 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -20,7 +20,7 @@ jobs: include: - name: Path of Building lua_tree: PathOfBuilding - version: "2.63.0" + version: "2.66.2" bundle_name: "Path of Building" bundle_id: ch.spidy.PathOfBuildingMac app_support_dir: PathOfBuildingMac diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c73d49..ef10e07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,7 +86,7 @@ set(POB_RES_DEST "${POB_BUNDLE_NAME}.app/Contents/Resources" add_subdirectory(SimpleGraphic) # ----- Bundle target ----- -set(POB_BUNDLE_VERSION "2.63.0" +set(POB_BUNDLE_VERSION "2.66.2" CACHE STRING "PoB version — drives Info.plist and the App Support refresh marker") set(POB_APPICON "${CMAKE_CURRENT_SOURCE_DIR}/macos/AppIcon.icns") diff --git a/MACOS_PORT.md b/MACOS_PORT.md index 2da4269..20bd34c 100644 --- a/MACOS_PORT.md +++ b/MACOS_PORT.md @@ -48,7 +48,7 @@ Path of Building.app/ │ ├── Resources/ │ │ ├── AppIcon.icns │ │ ├── SimpleGraphic/Fonts/ bitmap fonts -│ │ ├── lua/ runtime/lua from PoB (dkjson etc.) +│ │ ├── lua/ bundled runtime/lua fallback (dkjson etc.) │ │ └── src/ bundled PoB Lua tree — "factory default" │ │ ├── Launch.lua upstream entry │ │ ├── mac_entry.lua our bootstrap (not in any manifest) @@ -75,6 +75,7 @@ Two App Support directories, deliberately separate: └── PathOfBuildingMac/ our wrapper runtime state ├── .bundle_version refresh-detection marker (compared to compile-time constant) ├── src/ relocated Lua tree — this is PoB's live working directory + │ ├── lua/ updater-managed runtime/lua modules │ └── … updater writes here, cfg files resolve here ├── SimpleGraphic/ │ ├── SimpleGraphic.cfg SG's window state @@ -98,7 +99,7 @@ What it does, in order: 3. **Load `libSimpleGraphic.dylib`** from the same directory and `dlsym` the single export `RunLuaFileAsWin`. 4. **Detect bundle vs. dev mode** by checking if `/../Resources/src/Launch.lua` exists. In bundle mode the launcher ignores argv entirely and auto-discovers paths; in dev mode argv[1] is a path to a Lua script and paths are derived relative to it (matches PR #98's `linux/launcher.c` pattern). 5. **Relocate `src/` into App Support** (bundle mode only) — see next section. -6. **Set environment variables** for SG: `SG_BASE_PATH` (always points at bundle `Contents/Resources` — fonts and runtime lua stay in the bundle as read-only data), `POB_MAC_USER_DIR` (points at `PathOfBuildingMac/`), `LUA_PATH`, `LUA_CPATH`. +6. **Set environment variables** for SG: `SG_BASE_PATH` (always points at bundle `Contents/Resources` for fonts and bundled Lua fallbacks), `POB_MAC_USER_DIR` (points at `PathOfBuildingMac/`), `LUA_PATH`, `LUA_CPATH`. `LUA_PATH` searches `PathOfBuildingMac/src/lua` first so updater-managed runtime Lua modules can override the bundled fallback. 7. **Call `RunLuaFileAsWin(1, { scriptPath, NULL })`** where `scriptPath` is `/PathOfBuildingMac/src/mac_entry.lua`. SG then sets its internal `scriptPath` to the parent directory and everything downstream runs out of App Support. `CMAKE_CXX_STANDARD 17` is set at wrapper scope in the top-level `CMakeLists.txt` because `std::filesystem` and `std::optional` need it. @@ -120,7 +121,7 @@ PoB on Windows writes updater-fetched Lua files directly into its install tree, - `/Applications/Path of Building.app` is not user-writable without admin (the updater would `EACCES`) - A signed bundle's `_CodeSignature/CodeResources` seals everything inside `Contents/`; any write invalidates `codesign --verify --deep --strict` -Option B splits the tree: the bundle ships a read-only "factory default" Lua tree, and `src/` lives for real at a user-writable App Support location that the updater can freely modify. The signed bundle is never touched after install. +Option B splits the tree: the bundle ships a read-only "factory default" Lua tree plus runtime Lua fallback modules, and `src/` lives for real at a user-writable App Support location that the updater can freely modify. The macOS manifest filter rewrites upstream `runtime/lua/*.lua` entries into `src/lua/` updates so new Lua dependencies arrive with in-app updates, while native runtime files and fonts stay bundle-managed. The signed bundle is never touched after install. ### Version-bump scenario (validated end-to-end) diff --git a/PathOfBuilding b/PathOfBuilding index b3b4bc7..b23da8f 160000 --- a/PathOfBuilding +++ b/PathOfBuilding @@ -1 +1 @@ -Subproject commit b3b4bc74536d42a5fadb7eec783e8d63a7f0e68b +Subproject commit b23da8f841e4b0bc167b0b4401ea002d7d45f807 diff --git a/macos/launcher.cpp b/macos/launcher.cpp index f1788fd..c618470 100644 --- a/macos/launcher.cpp +++ b/macos/launcher.cpp @@ -304,14 +304,15 @@ int main(int argc, char** argv) strncpy(scriptAbs, scriptPath.c_str(), sizeof(scriptAbs)); scriptAbs[sizeof(scriptAbs) - 1] = '\0'; - // SG_BASE_PATH keeps pointing at the bundle's Contents/Resources — - // fonts and runtime/lua are read-only and don't need to move. + // SG_BASE_PATH keeps pointing at the bundle's Contents/Resources for + // fonts and bundled Lua-module fallbacks. Lua modules updated by PoB's + // in-app updater live under App Support src/lua and are searched first. strncpy(sgBasePath, bundleResourcesAbs, sizeof(sgBasePath)); sgBasePath[sizeof(sgBasePath) - 1] = '\0'; std::snprintf(luaPath, sizeof(luaPath), - "%s/lua/?.lua;%s/lua/?/init.lua;;", - bundleResourcesAbs, bundleResourcesAbs); + "%s/src/lua/?.lua;%s/src/lua/?/init.lua;%s/lua/?.lua;%s/lua/?/init.lua;;", + appSupport->c_str(), appSupport->c_str(), bundleResourcesAbs, bundleResourcesAbs); } else { // Dev: argv[1] is the Lua script path, derive everything from it. if (argc < 2) { diff --git a/macos/mac_entry.lua b/macos/mac_entry.lua index ce8d827..0818f61 100644 --- a/macos/mac_entry.lua +++ b/macos/mac_entry.lua @@ -12,10 +12,10 @@ -- lcurl.dll, Path of Building.exe, etc. from raw.githubusercontent.com -- and crash on the runtime source URL lookup. We monkey-patch -- xml.LoadXMLFile and xml.ParseXML to strip win32-only file entries, --- drop cross-platform part="runtime" files (which would otherwise +-- drop non-Lua cross-platform part="runtime" files (which would otherwise -- trigger basic-mode updates that require an external Update helper --- binary we don't ship), and re-key Windows-only source URLs to --- platform="macos". +-- binary we don't ship), and re-key runtime/lua files to a macOS-only +-- pseudo part that updates into writable App Support. -- -- Patch 2: UpdateCheck.lua MakeDir loop fix for POSIX absolute paths -- UpdateCheck.lua creates destination directories before downloading @@ -83,41 +83,60 @@ local function filterPoBVersion(doc) end local root = doc[1] local filtered = { elem = "PoBVersion", attrib = root.attrib } + local function copyAttrib(attrib) + local copy = {} + for k, v in pairs(attrib or {}) do copy[k] = v end + return copy + end + local function isRuntimeLuaFile(node) + return node.elem == "File" + and node.attrib + and node.attrib.part == "runtime" + and type(node.attrib.name) == "string" + and node.attrib.name:match("^lua/.+%.lua$") + end for _, node in ipairs(root) do local keep = true if type(node) == "table" then if node.elem == "File" and node.attrib.runtime and node.attrib.runtime ~= "macos" then -- Drop Windows-only binaries (.dll, .exe). keep = false + elseif isRuntimeLuaFile(node) then + -- Runtime Lua modules are cross-platform source files. Track + -- them as a macOS pseudo part so they download from upstream's + -- runtime/ URL but install under writable App Support src/lua/. + local copy = { elem = "File", attrib = copyAttrib(node.attrib) } + copy.attrib.part = "runtime_lua" + table.insert(filtered, copy) + keep = false + elseif node.elem == "Source" and node.attrib.part == "runtime" then + -- Upstream exposes the runtime source with platform="win32" + -- because Windows is the only platform that updates the whole + -- runtime. On macOS we only use it for runtime/lua modules. + local copy = { elem = "Source", attrib = copyAttrib(node.attrib) } + copy.attrib.part = "runtime_lua" + copy.attrib.platform = "macos" + table.insert(filtered, copy) + keep = false elseif node.elem == "File" and node.attrib.part == "runtime" then - -- Cross-platform "runtime" file (e.g. lua/dkjson.lua, fonts - -- under SimpleGraphic/Fonts/). Upstream marks these - -- part="runtime" because they live in runtime/ in the PoB - -- Lua repo, but functionally they're regular data files. - -- We drop them from the manifest entirely on macOS for two - -- reasons: + -- Cross-platform non-Lua runtime files (fonts under + -- SimpleGraphic/Fonts/). Upstream marks these part="runtime" + -- because they live in runtime/ in the PoB Lua repo. We drop + -- them from the manifest entirely on macOS for two reasons: -- 1. UpdateCheck.lua flips updateMode to "basic" for ANY -- part="runtime" file, which on macOS would call -- SpawnProcess(runtimePath/Update, ...) — we don't -- ship an Update helper binary, so basic-mode would -- fail forever. - -- 2. We can't simply re-categorize them as "program" - -- because that would route the download URL through - -- /master/src/ instead of /master/runtime/ - -- and change the on-disk destination from Resources/ - -- to Resources/src/. -- They're shipped in the DMG, sit on disk, and get -- refreshed only when a new DMG is released. These files -- are extremely stable upstream (years between changes), -- so this is acceptable. keep = false elseif node.elem == "Source" and node.attrib.platform and node.attrib.platform ~= "macos" then - -- Re-key Windows-only source URLs to macos so the platform-keyed - -- partSources lookup in UpdateCheck.lua resolves. The cross-platform - -- files served from this URL (runtime/lua/*, runtime/SimpleGraphic/*) - -- live at the same upstream raw URL regardless of target platform. - local copy = { elem = "Source", attrib = {} } - for k, v in pairs(node.attrib) do copy.attrib[k] = v end + -- Re-key any remaining platform-specific sources to macos so + -- UpdateCheck.lua's platform-keyed partSources lookup resolves. + local copy = { elem = "Source", attrib = copyAttrib(node.attrib) } copy.attrib.platform = "macos" table.insert(filtered, copy) keep = false @@ -228,18 +247,40 @@ local function filterPoBVersion(doc) end local root = doc[1] local filtered = { elem = "PoBVersion", attrib = root.attrib } + local function copyAttrib(attrib) + local copy = {} + for k, v in pairs(attrib or {}) do copy[k] = v end + return copy + end + local function isRuntimeLuaFile(node) + return node.elem == "File" + and node.attrib + and node.attrib.part == "runtime" + and type(node.attrib.name) == "string" + and node.attrib.name:match("^lua/.+%.lua$") + end for _, node in ipairs(root) do local keep = true if type(node) == "table" then if node.elem == "File" and node.attrib.runtime and node.attrib.runtime ~= "macos" then keep = false + elseif isRuntimeLuaFile(node) then + local copy = { elem = "File", attrib = copyAttrib(node.attrib) } + copy.attrib.part = "runtime_lua" + table.insert(filtered, copy) + keep = false + elseif node.elem == "Source" and node.attrib.part == "runtime" then + local copy = { elem = "Source", attrib = copyAttrib(node.attrib) } + copy.attrib.part = "runtime_lua" + copy.attrib.platform = "macos" + table.insert(filtered, copy) + keep = false elseif node.elem == "File" and node.attrib.part == "runtime" then - -- Drop cross-platform runtime files entirely. See main-state - -- filter above for why we don't re-categorize them. + -- Drop cross-platform non-Lua runtime files entirely. See + -- main-state filter above for the basic-mode constraint. keep = false elseif node.elem == "Source" and node.attrib.platform and node.attrib.platform ~= "macos" then - local copy = { elem = "Source", attrib = {} } - for k, v in pairs(node.attrib) do copy.attrib[k] = v end + local copy = { elem = "Source", attrib = copyAttrib(node.attrib) } copy.attrib.platform = "macos" table.insert(filtered, copy) keep = false From 887d5fdf116cfb0a6980d0b6cb71a1e4152e16a2 Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Sun, 26 Jul 2026 12:17:18 +0200 Subject: [PATCH 2/2] fix: run macOS bundle without Retina DPI mode Disable the macOS high-resolution backing surface and filter PoB's DPI_AWARE renderer flag in the wrapper. This avoids the external-display startup state where SimpleGraphic renders into the lower-left quadrant until the window is dragged across monitors. --- MACOS_PORT.md | 4 +++- macos/Info.plist.in | 2 +- macos/mac_entry.lua | 20 +++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/MACOS_PORT.md b/MACOS_PORT.md index 20bd34c..33c117e 100644 --- a/MACOS_PORT.md +++ b/MACOS_PORT.md @@ -113,6 +113,7 @@ Every bundle-mode launch calls `RelocateRuntime(bundleSrc)`: 3. **If marker matches**: no-op for `src/`. The extracted tree is up-to-date for this DMG, and any in-app updater state accumulated since is preserved. 4. **If marker is missing or stale**: `fs::remove_all(appSrc)` then `fs::copy(bundleSrc, appSrc, fs::copy_options::recursive)`, then write the new marker. Logged to stderr as `RelocateRuntime: bundle version changed (marker=X, bundle=Y), refreshing …`. 5. **Always** overwrite `PathOfBuildingMac/src/mac_entry.lua` from the bundle. It's our bootstrap file, PoB's updater never touches it (excluded from the manifest), and keeping it always-fresh lets dev iteration on `mac_entry.lua` take effect on next launch without needing a `POB_BUNDLE_VERSION` bump. +6. The bundle opts out of a Retina backing surface (`NSHighResolutionCapable=false`), and `mac_entry.lua` filters PoB 3.29+'s `RenderInit("DPI_AWARE")` flag. SimpleGraphic's macOS/ANGLE path can otherwise initialize with a Retina drawable while the stored framebuffer size remains logical on some external displays, rendering PoB into the lower-left quadrant until the window is dragged across monitors. ### Why this shape @@ -248,8 +249,9 @@ All documented in [UPSTREAM_NOTES.md](UPSTREAM_NOTES.md) with file/line/fix deta 9. ANGLE `liblibEGL.dylib` double-lib-prefix on macOS. 10. `UpdateCheck.lua` MakeDir POSIX-absolute-path loop bug — the hanging updater on first fresh install. 11. SG config files (`SimpleGraphic.cfg` / `imgui.ini`) write to `basePath` instead of `userPath`. +12. PoB 3.29 `DPI_AWARE` rendering can start with mismatched logical framebuffer dimensions on macOS external displays; the wrapper currently avoids that path by running non-Retina. -All eleven are things that belong upstream — independent of our wrapper-specific Option B architecture. +All twelve are things that belong upstream — independent of our wrapper-specific Option B architecture. ## Distribution pipeline — still pending diff --git a/macos/Info.plist.in b/macos/Info.plist.in index 957b614..4750ba1 100644 --- a/macos/Info.plist.in +++ b/macos/Info.plist.in @@ -25,7 +25,7 @@ LSMinimumSystemVersion 11.0 NSHighResolutionCapable - + NSPrincipalClass NSApplication NSHumanReadableCopyright diff --git a/macos/mac_entry.lua b/macos/mac_entry.lua index 0818f61..6a4f80c 100644 --- a/macos/mac_entry.lua +++ b/macos/mac_entry.lua @@ -43,7 +43,13 @@ -- SimpleGraphic's MakeDir implementation. See UPSTREAM_NOTES.md at -- the wrapper repo root for the upstreamable bug report. -- --- Both patches need two installation points because UpdateCheck runs in +-- Patch 3: renderer DPI mode +-- PoB 3.29+ calls RenderInit("DPI_AWARE"). On macOS with some external +-- display arrangements, SimpleGraphic can initialize with stale framebuffer +-- scale until the window is dragged across displays. Filtering the flag lets +-- SimpleGraphic use logical window coordinates consistently on macOS. +-- +-- The updater patches need two installation points because UpdateCheck runs in -- two places: -- a. The first-run path (Launch.lua line ~37) calls LoadModule("UpdateCheck") -- in the main Lua state. We wrap LoadModule globally, read the file @@ -77,6 +83,18 @@ function GetVirtualScreenSize() return width, height end +local rawRenderInit = RenderInit +function RenderInit(...) + local filtered = {} + for i = 1, select("#", ...) do + local arg = select(i, ...) + if arg ~= "DPI_AWARE" then + filtered[#filtered + 1] = arg + end + end + return rawRenderInit(unpack(filtered)) +end + local function filterPoBVersion(doc) if type(doc) ~= "table" or not doc[1] or doc[1].elem ~= "PoBVersion" then return doc