Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
11 changes: 7 additions & 4 deletions MACOS_PORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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 `<exeDir>/../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 `<AppSupport>/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.
Expand All @@ -112,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

Expand All @@ -120,7 +122,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)

Expand Down Expand Up @@ -247,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

Expand Down
2 changes: 1 addition & 1 deletion PathOfBuilding
Submodule PathOfBuilding updated 345 files
2 changes: 1 addition & 1 deletion macos/Info.plist.in
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<false/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHumanReadableCopyright</key>
Expand Down
9 changes: 5 additions & 4 deletions macos/launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
109 changes: 84 additions & 25 deletions macos/mac_entry.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -77,47 +83,78 @@ 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
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
-- <repo>/master/src/ instead of <repo>/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
Expand Down Expand Up @@ -228,18 +265,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
Expand Down