From 1d9c302410aa8179f8cee46440fad45bae48bcca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gon=C3=A7alves=20da=20Silva?= Date: Thu, 30 Jul 2026 18:47:40 -0700 Subject: [PATCH 1/3] Add Apple Silicon SimpleGraphic smoke app --- .gitattributes | 1 + .gitignore | 3 +- CMakeLists.txt | 127 ++++++++++++++++-- cmake/FindLuaJIT.cmake | 6 +- engine/common/base64.c | 1 + engine/common/base64.h | 1 + engine/common/common.cpp | 4 +- engine/render/r_font.cpp | 2 +- engine/render/r_main.cpp | 4 +- engine/render/r_texture.cpp | 5 +- engine/system/win/sys_main.cpp | 8 +- engine/system/win/sys_video.cpp | 16 ++- macos/Info.plist.in | 22 +++ macos/README.md | 52 +++++++ macos/launcher.c | 27 ++++ macos/smoke/Launch.lua | 61 +++++++++ macos/verify-bundle.sh | 42 ++++++ ...-cURLv3-skip-luaL_setfuncs-on-luajit.patch | 38 ++++++ ui_api.cpp | 13 +- ui_main.cpp | 12 ++ ui_main.h | 1 + ui_subscript.cpp | 1 + .../ports/luajit/2026-07-20_1/configure | 1 - .../luajit-internal-unwinding-on-macos.patch | 9 ++ .../luajit/2026-07-20_1/pob-wide-crt.patch | 2 +- .../ports/luajit/2026-07-20_1/portfile.cmake | 10 +- vcpkg-ports/triplets/arm64-osx.cmake | 15 +++ vcpkg.json | 3 +- 28 files changed, 445 insertions(+), 42 deletions(-) create mode 100644 .gitattributes create mode 100644 macos/Info.plist.in create mode 100644 macos/README.md create mode 100644 macos/launcher.c create mode 100644 macos/smoke/Launch.lua create mode 100755 macos/verify-bundle.sh create mode 100644 patches/Lua-cURLv3-skip-luaL_setfuncs-on-luajit.patch create mode 100644 vcpkg-ports/ports/luajit/2026-07-20_1/luajit-internal-unwinding-on-macos.patch create mode 100644 vcpkg-ports/triplets/arm64-osx.cmake diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..989a022 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +patches/* -text diff --git a/.gitignore b/.gitignore index f7bd29a..914a30d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /build/ +/build-*/ /build-SimpleGraphic -/.idea \ No newline at end of file +/.idea diff --git a/CMakeLists.txt b/CMakeLists.txt index b992d62..1c916d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,18 +78,18 @@ set(SIMPLEGRAPHIC_SOURCES set (SIMPLEGRAPHIC_PLATFORM_SOURCES) if (APPLE) - set (SIMPLEGRAPHIC_PLATFORM_SOURCES + list (APPEND SIMPLEGRAPHIC_PLATFORM_SOURCES "engine/system/win/sys_macos.mm" ) endif() if (WIN32) - set (SIMPLEGRAPHIC_PLATFORM_SOURCES + list (APPEND SIMPLEGRAPHIC_PLATFORM_SOURCES "engine/system/win/sys_console.cpp" "SimpleGraphic.rc" ) else() - set (SIMPLEGRAPHIC_PLATFORM_SOURCES + list (APPEND SIMPLEGRAPHIC_PLATFORM_SOURCES "engine/system/win/sys_console_unix.cpp" ) endif() @@ -131,6 +131,12 @@ find_package(zstd REQUIRED) find_package(ZLIB REQUIRED) find_package(WebP) +if (TARGET zstd::libzstd_shared) + set(ZSTD_LIBRARY zstd::libzstd_shared) +else () + set(ZSTD_LIBRARY zstd::libzstd_static) +endif () + add_library(cmp_core STATIC dep/compressonator/cmp_core/source/cmp_core.cpp dep/compressonator/cmp_core/source/cmp_core.h @@ -240,9 +246,17 @@ target_link_libraries(SimpleGraphic Threads::Threads WebP::webpdecoder ZLIB::ZLIB - zstd::libzstd_shared + ${ZSTD_LIBRARY} ) +if (APPLE) + set_target_properties(SimpleGraphic PROPERTIES + BUILD_WITH_INSTALL_NAME_DIR TRUE + INSTALL_NAME_DIR "@rpath" + MACOSX_RPATH TRUE + ) +endif () + install(FILES $ DESTINATION ".") install(TARGETS SimpleGraphic RUNTIME DESTINATION ".") @@ -276,6 +290,26 @@ if (WIN32) ) endif () +if (APPLE) + find_package(Git REQUIRED) + execute_process( + COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR}/libs/Lua-cURLv3 + apply --ignore-whitespace --reverse --check + ${CMAKE_CURRENT_SOURCE_DIR}/patches/Lua-cURLv3-skip-luaL_setfuncs-on-luajit.patch + RESULT_VARIABLE LCURL_PATCHED + OUTPUT_QUIET + ERROR_QUIET + ) + if (NOT LCURL_PATCHED EQUAL 0) + execute_process( + COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR}/libs/Lua-cURLv3 + apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/Lua-cURLv3-skip-luaL_setfuncs-on-luajit.patch + COMMAND_ERROR_IS_FATAL ANY + ) + endif () +endif () + # lcurl module @@ -301,10 +335,14 @@ install(FILES $ DESTINATION ".") add_library(lua-utf8 SHARED libs/luautf8/lutf8lib.c) -target_compile_definitions(lua-utf8 - PRIVATE - LUA_BUILD_AS_DLL -) +if (WIN32) + target_compile_definitions(lua-utf8 + PRIVATE + LUA_BUILD_AS_DLL + ) +elseif (APPLE) + target_compile_options(lua-utf8 PRIVATE -include limits.h) +endif () target_include_directories(lua-utf8 PRIVATE @@ -333,9 +371,14 @@ add_library(luasocket SHARED "libs/luasocket/src/tcp.c" "libs/luasocket/src/timeout.c" "libs/luasocket/src/udp.c" - "libs/luasocket/src/wsocket.c" ) +if (WIN32) + target_sources(luasocket PRIVATE "libs/luasocket/src/wsocket.c") +else () + target_sources(luasocket PRIVATE "libs/luasocket/src/usocket.c") +endif () + target_include_directories(luasocket PRIVATE ${LSOCKET_SOURCE_DIR}/src @@ -344,10 +387,12 @@ target_include_directories(luasocket target_link_libraries(luasocket PRIVATE LuaJIT::LuaJIT - wsock32 - ws2_32 ) +if (WIN32) + target_link_libraries(luasocket PRIVATE wsock32 ws2_32) +endif () + set_target_properties( luasocket PROPERTIES OUTPUT_NAME "socket" ) install(TARGETS luasocket RUNTIME DESTINATION ".") install(FILES $ DESTINATION ".") @@ -368,3 +413,63 @@ target_link_libraries(lzip install(TARGETS lzip RUNTIME DESTINATION ".") install(FILES $ DESTINATION ".") + +if (APPLE) + set_target_properties(lcurl lua-utf8 luasocket lzip PROPERTIES + BUILD_WITH_INSTALL_RPATH TRUE + INSTALL_RPATH "@loader_path/../Frameworks" + PREFIX "" + SUFFIX ".so" + ) + + set_source_files_properties(macos/smoke/Launch.lua PROPERTIES + MACOSX_PACKAGE_LOCATION Resources + ) + add_executable(SimpleGraphicSmoke MACOSX_BUNDLE + macos/launcher.c + macos/smoke/Launch.lua + ) + target_link_libraries(SimpleGraphicSmoke PRIVATE SimpleGraphic) + add_dependencies(SimpleGraphicSmoke lcurl lua-utf8 luasocket lzip) + set_target_properties(SimpleGraphicSmoke PROPERTIES + BUILD_WITH_INSTALL_RPATH TRUE + INSTALL_RPATH "@executable_path/../Frameworks" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in" + MACOSX_BUNDLE_BUNDLE_NAME "SimpleGraphicSmoke" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.pathofbuilding.simplegraphic.smoke" + ) + + add_custom_command(TARGET SimpleGraphicSmoke POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + "$/../Frameworks" + "$/../Resources/lcurl" + "$/../Resources/socket" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$" + "$" + "$/../Frameworks" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$/../Frameworks/libEGL.dylib" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$/../Frameworks/libGLESv2.dylib" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$/../Resources/lcurl.so" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$/../Resources/lcurl/safe.so" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$/../Resources/lua-utf8.so" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$/../Resources/socket/core.so" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$/../Resources/lzip.so" + VERBATIM + ) +endif () diff --git a/cmake/FindLuaJIT.cmake b/cmake/FindLuaJIT.cmake index a037bf5..3638100 100644 --- a/cmake/FindLuaJIT.cmake +++ b/cmake/FindLuaJIT.cmake @@ -9,15 +9,15 @@ if (DEFINED VCPKG_INSTALLED_DIR AND DEFINED VCPKG_TARGET_TRIPLET) find_path(LuaJIT_INCLUDE_DIR luajit.h PATHS ${LuaJIT_SEARCH_ROOT}/include - PATH_SUFFIXES luajit + PATH_SUFFIXES luajit luajit-2.0 luajit-2.1 NO_DEFAULT_PATH) - find_library(LuaJIT_LIBRARY_RELEASE NAMES lua51 + find_library(LuaJIT_LIBRARY_RELEASE NAMES lua51 luajit-5.1 PATHS ${LuaJIT_SEARCH_ROOT} PATH_SUFFIXES lib NO_DEFAULT_PATH) - find_library(LuaJIT_LIBRARY_DEBUG NAMES lua51 + find_library(LuaJIT_LIBRARY_DEBUG NAMES lua51 luajit-5.1 PATHS ${LuaJIT_SEARCH_ROOT} PATH_SUFFIXES debug/lib NO_DEFAULT_PATH) diff --git a/engine/common/base64.c b/engine/common/base64.c index c02a2b6..9e3ddf3 100644 --- a/engine/common/base64.c +++ b/engine/common/base64.c @@ -29,6 +29,7 @@ #include "base64.h" #include +#include /* ---- Base64 Encoding/Decoding Table --- */ /* Padding character string starts at offset 64. */ diff --git a/engine/common/base64.h b/engine/common/base64.h index 5cb097f..e23e374 100644 --- a/engine/common/base64.h +++ b/engine/common/base64.h @@ -26,6 +26,7 @@ // Modified for standalone inclusion in SimpleGraphic. #include +#include #ifdef __cplusplus extern "C" { diff --git a/engine/common/common.cpp b/engine/common/common.cpp index 48d8df3..ba84a65 100644 --- a/engine/common/common.cpp +++ b/engine/common/common.cpp @@ -437,6 +437,8 @@ char* NarrowUTF8String(const wchar_t* str) return NarrowCodepageString(str, CP_UTF8); } +#endif + IndexedUTF32String IndexUTF8ToUTF32(std::string_view input) { IndexedUTF32String ret{}; @@ -500,5 +502,3 @@ IndexedUTF32String IndexUTF8ToUTF32(std::string_view input) ret.text = std::u32string(codepoints.begin(), codepoints.end()); return ret; } - -#endif diff --git a/engine/render/r_font.cpp b/engine/render/r_font.cpp index 90375b3..71fa186 100644 --- a/engine/render/r_font.cpp +++ b/engine/render/r_font.cpp @@ -321,7 +321,7 @@ r_font_c::FontHeightEntry r_font_c::FindFontHeight(int height) { void r_font_c::DrawTextLine(scp_t pos, int align, int height, col4_t col, std::u32string_view str) { // Check if the line is visible - if (pos[Y] >= renderer->sys->video->vid.size[1] || pos[Y] <= -height) { + if (pos[Y] >= renderer->VirtualScreenHeight() || pos[Y] <= -height) { // Just process the colour codes while (!str.empty()) { // Check for escape character diff --git a/engine/render/r_main.cpp b/engine/render/r_main.cpp index 1d46715..9b8c04f 100644 --- a/engine/render/r_main.cpp +++ b/engine/render/r_main.cpp @@ -12,6 +12,8 @@ #include #include +#include +#include #include #include #include @@ -1640,7 +1642,7 @@ void r_renderer_c::GetShaderImageSize(r_shaderHnd_c* hnd, int& width, int& heigh if (hnd) { while (hnd->sh->tex->status < r_tex_c::SIZE_KNOWN) { - Sleep(1); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } width = hnd->sh->tex->fileWidth; height = hnd->sh->tex->fileHeight; diff --git a/engine/render/r_texture.cpp b/engine/render/r_texture.cpp index 07fdda8..0b049ef 100644 --- a/engine/render/r_texture.cpp +++ b/engine/render/r_texture.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "r_local.h" #include "cmp_core.h" @@ -504,7 +505,7 @@ static gli::texture2d_array TranscodeTexture(gli::texture2d_array src, gli::form for (size_t blockRow = 0; blockRow < srcBlocksPerRow; ++blockRow) { const size_t rowBase = blockRow * srcBlockSize.y; - const size_t rowsLeft = (std::min)(4ull, dstExtent.y - rowBase); + const size_t rowsLeft = (std::min)(size_t(4), dstExtent.y - rowBase); for (size_t blockCol = 0; blockCol < srcBlocksPerColumn; ++blockCol) { // Read source 4x4 texel block, no branching needed. @@ -524,7 +525,7 @@ static gli::texture2d_array TranscodeTexture(gli::texture2d_array src, gli::form // Here we work off that dstData points at the top left pixel of the block row in the destination. const size_t colBase = blockCol * srcBlockSize.x; - const size_t colsLeft = (std::min)(4ull, dstExtent.x - colBase); + const size_t colsLeft = (std::min)(size_t(4), dstExtent.x - colBase); const size_t colBytesLeft = colsLeft * 4; for (size_t innerRow = 0; innerRow < rowsLeft; ++innerRow) { auto* dstPtr = dstData + dstRowStride * innerRow + colBase * 4; diff --git a/engine/system/win/sys_main.cpp b/engine/system/win/sys_main.cpp index 3078e0d..468948b 100644 --- a/engine/system/win/sys_main.cpp +++ b/engine/system/win/sys_main.cpp @@ -576,7 +576,13 @@ std::filesystem::path FindBasePath() progPath = basePath; #endif progPath = weakly_canonical(progPath); - return progPath.parent_path(); + auto dirPath = progPath.parent_path(); +#if __APPLE__ && __MACH__ + if (dirPath.filename() == "MacOS" && dirPath.parent_path().filename() == "Contents") { + dirPath = dirPath.parent_path() / "Resources"; + } +#endif + return dirPath; } std::tuple, std::optional> FindUserPath() diff --git a/engine/system/win/sys_video.cpp b/engine/system/win/sys_video.cpp index 6f61bff..4c056b7 100644 --- a/engine/system/win/sys_video.cpp +++ b/engine/system/win/sys_video.cpp @@ -115,6 +115,8 @@ sys_video_c::sys_video_c(sys_IMain* sysHnd) platformType = GLFW_ANGLE_PLATFORM_TYPE_D3D11; else // Native Windows platformType = GLFW_ANGLE_PLATFORM_TYPE_D3D11; +#elif defined(__APPLE__) + platformType = GLFW_ANGLE_PLATFORM_TYPE_METAL; #endif glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE, platformType); glfwInit(); @@ -489,7 +491,9 @@ int sys_video_c::Apply(sys_vidSet_s* set) return; } auto video = (sys_video_c*)sys->video; - video->lastCursorPos = CursorPos{ (int)x, (int)y }; + double sx = video->vid.size[0] > 0 ? (double)video->vid.fbSize[0] / video->vid.size[0] : 1.0; + double sy = video->vid.size[1] > 0 ? (double)video->vid.fbSize[1] / video->vid.size[1] : 1.0; + video->lastCursorPos = CursorPos{ (int)(x * sx), (int)(y * sy) }; }); glfwSetWindowCloseCallback(wnd, [](GLFWwindow* wnd) { auto sys = (sys_main_c*)glfwGetWindowUserPointer(wnd); @@ -737,14 +741,18 @@ void sys_video_c::GetRelativeCursor(int& x, int& y) if (!initialised) return; double xpos, ypos; glfwGetCursorPos(wnd, &xpos, &ypos); - x = (int)floor(xpos); - y = (int)floor(ypos); + double sx = vid.size[0] > 0 ? (double)vid.fbSize[0] / vid.size[0] : 1.0; + double sy = vid.size[1] > 0 ? (double)vid.fbSize[1] / vid.size[1] : 1.0; + x = (int)floor(xpos * sx); + y = (int)floor(ypos * sy); } void sys_video_c::SetRelativeCursor(int x, int y) { if (!initialised) return; - glfwSetCursorPos(wnd, (double)x, (double)y); + double sx = vid.fbSize[0] > 0 ? (double)vid.size[0] / vid.fbSize[0] : 1.0; + double sy = vid.fbSize[1] > 0 ? (double)vid.size[1] / vid.fbSize[1] : 1.0; + glfwSetCursorPos(wnd, x * sx, y * sy); } bool sys_video_c::IsCursorOverWindow() diff --git a/macos/Info.plist.in b/macos/Info.plist.in new file mode 100644 index 0000000..b2455a7 --- /dev/null +++ b/macos/Info.plist.in @@ -0,0 +1,22 @@ + + + + + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 13.0 + NSHighResolutionCapable + + + diff --git a/macos/README.md b/macos/README.md new file mode 100644 index 0000000..6899d1c --- /dev/null +++ b/macos/README.md @@ -0,0 +1,52 @@ +# Apple Silicon smoke app + +`SimpleGraphicSmoke.app` is the first native Apple Silicon SimpleGraphic +artifact. It uses the same LuaJIT host, Lua modules, GLFW Cocoa window, and +ANGLE Metal renderer intended for Path of Building integration. + +## Build + +The build requires Xcode command-line tools, CMake, Git, and an Apple Silicon +Mac. Dependencies are built by the pinned vcpkg submodule. + +```sh +git submodule update --init --recursive + +MACOSX_DEPLOYMENT_TARGET=13.0 cmake -S . -B build-macos13 \ + -G "Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake \ + -DVCPKG_OVERLAY_TRIPLETS="$PWD/vcpkg-ports/triplets" \ + -DVCPKG_TARGET_TRIPLET=arm64-osx \ + -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 + +cmake --build build-macos13 --target SimpleGraphicSmoke --parallel +``` + +The artifact is written to `build-macos13/SimpleGraphicSmoke.app`. Run its +executable from a terminal to retain the smoke log: + +```sh +build-macos13/SimpleGraphicSmoke.app/Contents/MacOS/SimpleGraphicSmoke +``` + +A passing run reports LuaJIT and native module loading, compression, paths, +localhost sockets, ANGLE rendering, and the host restart loop before exiting. + +## Verify + +```sh +macos/verify-bundle.sh build-macos13/SimpleGraphicSmoke.app +``` + +The verifier rejects non-arm64 Mach-O files, deployment targets newer than +macOS 13, absolute non-system dependencies, and invalid ad-hoc signatures. + +## Current scope + +The smoke app proves the native host and packaging seam. It does not yet prove +bitmap fonts, PNG/WebP and DDS/BC fixture rendering, screenshots, clipboard, +URL opening, HTTPS trust, or full Path of Building startup. It also still needs +a run on physical Apple Silicon macOS 13 and a green Windows CI build before it +is ready to hand off to Path of Building integration. diff --git a/macos/launcher.c b/macos/launcher.c new file mode 100644 index 0000000..b22af65 --- /dev/null +++ b/macos/launcher.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include + +extern int RunLuaFileAsWin(int argc, char **argv); + +int main(int argc, char **argv) +{ + char executablePath[4096]; + uint32_t size = sizeof(executablePath); + if (_NSGetExecutablePath(executablePath, &size) == 0) { + char resourcesPath[4096]; + snprintf(resourcesPath, sizeof(resourcesPath), "%s/../Resources", dirname(executablePath)); + if (chdir(resourcesPath) != 0) { + perror("Could not enter app resources directory"); + return 1; + } + } + + if (argc > 1) { + return RunLuaFileAsWin(argc - 1, argv + 1); + } + char *defaultArgs[] = { "Launch.lua" }; + return RunLuaFileAsWin(1, defaultArgs); +} diff --git a/macos/smoke/Launch.lua b/macos/smoke/Launch.lua new file mode 100644 index 0000000..6f81b95 --- /dev/null +++ b/macos/smoke/Launch.lua @@ -0,0 +1,61 @@ +#@ SimpleGraphic + +local restartMarker = "/tmp/org.pathofbuilding.simplegraphic-smoke-restart" +local marker = io.open(restartMarker, "r") +local restarted = marker ~= nil +if marker then + marker:close() + os.remove(restartMarker) +end + +local smoke = { frames = 0, restarted = restarted } +SetMainObject(smoke) +SetWindowTitle("SimpleGraphic macOS smoke") +ConExecute("set vid_mode 8") +ConExecute("set vid_resizable 3") + +function smoke:OnInit() + RenderInit("DPI_AWARE") + SetClearColor(0.04, 0.08, 0.12, 1) + + local payload = "SimpleGraphic macOS arm64 smoke" + assert(Inflate(Deflate(payload)) == payload) + assert(type(GetScriptPath()) == "string") + assert(type(GetRuntimePath()) == "string") + assert(type(GetUserPath()) == "string") + assert(require("lcurl.safe")) + assert(require("lua-utf8")) + assert(require("lzip")) + local socket = assert(require("socket.core")) + local listener = assert(socket.tcp()) + assert(listener:bind("127.0.0.1", 0)) + listener:close() + + self.subscript = assert(LaunchSubScript([[ + assert(require("lcurl.safe")) + assert(require("lua-utf8")) + assert(require("lzip")) + assert(require("socket.core")) + ]], "", "")) + ConPrintf("SMOKE: LuaJIT, native modules, compression, paths, and localhost socket passed.\n") +end + +function smoke:OnFrame() + local width, height = GetScreenSize() + SetDrawColor(0.12, 0.55, 0.82, 1) + DrawImage(nil, width / 4, height / 4, width / 2, height / 2) + self.frames = self.frames + 1 + if self.frames == 120 then + ConPrintf("SMOKE: ANGLE frame loop passed at %dx%d scale %.2f.\n", width, height, GetScreenScale()) + if self.restarted then + ConPrintf("SMOKE: restart loop passed; exiting cleanly.\n") + Exit() + else + local markerFile = assert(io.open(restartMarker, "w")) + markerFile:write("restart") + markerFile:close() + ConPrintf("SMOKE: restarting Lua host.\n") + Restart() + end + end +end diff --git a/macos/verify-bundle.sh b/macos/verify-bundle.sh new file mode 100755 index 0000000..ca0b876 --- /dev/null +++ b/macos/verify-bundle.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +set -eu + +bundle=${1:-} +if [ -z "$bundle" ] || [ ! -d "$bundle/Contents" ]; then + echo "usage: $0 SimpleGraphicSmoke.app" >&2 + exit 2 +fi + +status=0 +while IFS= read -r file_path; do + if ! file "$file_path" | grep -q "Mach-O"; then + continue + fi + + echo "== $file_path" + file "$file_path" + archs=$(lipo -archs "$file_path") + if [ "$archs" != "arm64" ]; then + echo "unexpected architectures: $archs" >&2 + status=1 + fi + + minos=$(vtool -show-build "$file_path" | awk '/minos/ { print $2; exit }') + case "$minos" in + 13|13.*) ;; + *) echo "unexpected deployment target: $minos" >&2; status=1 ;; + esac + + if otool -L "$file_path" | awk 'NR > 1 { print $1 }' | grep -Evq '^(@rpath/|@loader_path/|@executable_path/|/System/|/usr/lib/)'; then + echo "absolute non-system dependency found" >&2 + otool -L "$file_path" >&2 + status=1 + fi +done < /* for memset */ + #include + +-#if LUA_VERSION_NUM >= 502 ++/* LuaJIT (LUA_VERSION_NUM == 501) ships luaL_setfuncs as an extension; pull in ++ luajit.h so LUAJIT_VERSION is visible and the < 502 definition below can be ++ skipped, avoiding a duplicate-symbol link error against the LuaJIT library. */ ++#if defined(__has_include) ++# if __has_include() ++# include ++# endif ++#endif ++ ++#if LUA_VERSION_NUM >= 502 + + int luaL_typerror (lua_State *L, int narg, const char *tname) { + const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, +@@ -33,6 +42,7 @@ void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l){ + + #else + ++#if !defined(LUAJIT_VERSION) /* LuaJIT already provides luaL_setfuncs */ + void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup){ + luaL_checkstack(L, nup, "too many upvalues"); + for (; l->name != NULL; l++) { /* fill the table with given functions */ +@@ -44,6 +54,7 @@ void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup){ + } + lua_pop(L, nup); /* remove upvalues */ + } ++#endif + + void lua_rawgetp(lua_State *L, int index, const void *p){ + index = lua_absindex(L, index); diff --git a/ui_api.cpp b/ui_api.cpp index 6008350..3b7bc98 100644 --- a/ui_api.cpp +++ b/ui_api.cpp @@ -2172,18 +2172,7 @@ int ui_main_c::InitAPI(lua_State* L) sol::state_view lua(L); luaL_openlibs(L); - // Add "lua/" subdir for non-JIT Lua - { - lua_getglobal(L, "package"); - char const* tn = lua_typename(L, -1); - lua_getfield(L, -1, "path"); - std::string old_path = lua_tostring(L, -1); - lua_pop(L, 1); - old_path += ";lua/?.lua"; - lua_pushstring(L, old_path.c_str()); - lua_setfield(L, -2, "path"); - lua_pop(L, 1); - } + AppendLocalLuaSubdir(L); // Callbacks lua_newtable(L); // Callbacks table diff --git a/ui_main.cpp b/ui_main.cpp index 09e59be..4a099c2 100644 --- a/ui_main.cpp +++ b/ui_main.cpp @@ -184,6 +184,18 @@ void ui_main_c::DoError(const char* msg, const char* error) didExit = true; } +void ui_main_c::AppendLocalLuaSubdir(lua_State* L) +{ + lua_getglobal(L, "package"); + lua_getfield(L, -1, "path"); + std::string oldPath = lua_tostring(L, -1); + lua_pop(L, 1); + oldPath += ";lua/?.lua;lua/?/init.lua"; + lua_pushstring(L, oldPath.c_str()); + lua_setfield(L, -2, "path"); + lua_pop(L, 1); +} + // From lua.c static int traceback (lua_State *L) { if (!lua_isstring(L, 1)) /* 'message' not a string? */ diff --git a/ui_main.h b/ui_main.h index 02ec907..eb344e0 100644 --- a/ui_main.h +++ b/ui_main.h @@ -55,6 +55,7 @@ class ui_main_c: public ui_IMain { float lastColor[4] = {1.0f, 1.0f, 1.0f, 1.0f}; static int InitAPI(lua_State* L); + static void AppendLocalLuaSubdir(lua_State* L); void RenderInit(r_featureFlag_e features); void ScriptInit(); diff --git a/ui_subscript.cpp b/ui_subscript.cpp index befba5c..c03c2d6 100644 --- a/ui_subscript.cpp +++ b/ui_subscript.cpp @@ -322,6 +322,7 @@ bool ui_subscript_c::Start() // Add libraries and APIs lua_gc(L, LUA_GCSTOP, 0); luaL_openlibs(L); + ui_main_c::AppendLocalLuaSubdir(L); lua_getglobal(L, "os"); lua_pushcfunction(L, l_os_exit); lua_setfield(L, -2, "exit"); diff --git a/vcpkg-ports/ports/luajit/2026-07-20_1/configure b/vcpkg-ports/ports/luajit/2026-07-20_1/configure index 0384100..df9b19e 100644 --- a/vcpkg-ports/ports/luajit/2026-07-20_1/configure +++ b/vcpkg-ports/ports/luajit/2026-07-20_1/configure @@ -32,7 +32,6 @@ cat > Makefile.vcpkg < Date: Thu, 30 Jul 2026 18:53:21 -0700 Subject: [PATCH 2/3] Harden macOS smoke validation --- CMakeLists.txt | 2 ++ engine/system/win/sys_video.cpp | 22 +++++++++++++++------- macos/launcher.c | 7 +++++++ macos/smoke/Launch.lua | 22 ++++++++++++++++++---- macos/verify-bundle.sh | 30 +++++++++++++++++++++--------- 5 files changed, 63 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c916d0..e9c3cb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -470,6 +470,8 @@ if (APPLE) COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$/../Resources/lzip.so" + COMMAND /usr/bin/codesign --force --deep --sign - + "$" VERBATIM ) endif () diff --git a/engine/system/win/sys_video.cpp b/engine/system/win/sys_video.cpp index 4c056b7..827fff9 100644 --- a/engine/system/win/sys_video.cpp +++ b/engine/system/win/sys_video.cpp @@ -143,6 +143,17 @@ std::optional> PlatformGetCursorPos() { #endif } +std::pair CursorScale(const sys_vidSave_s& vid) { +#ifdef __APPLE__ + return { + vid.size[0] > 0 ? (double)vid.fbSize[0] / vid.size[0] : 1.0, + vid.size[1] > 0 ? (double)vid.fbSize[1] / vid.size[1] : 1.0 + }; +#else + return { 1.0, 1.0 }; +#endif +} + struct sys_programIcons_c { #if _WIN32 sys_programIcons_c() @@ -491,8 +502,7 @@ int sys_video_c::Apply(sys_vidSet_s* set) return; } auto video = (sys_video_c*)sys->video; - double sx = video->vid.size[0] > 0 ? (double)video->vid.fbSize[0] / video->vid.size[0] : 1.0; - double sy = video->vid.size[1] > 0 ? (double)video->vid.fbSize[1] / video->vid.size[1] : 1.0; + auto [sx, sy] = CursorScale(video->vid); video->lastCursorPos = CursorPos{ (int)(x * sx), (int)(y * sy) }; }); glfwSetWindowCloseCallback(wnd, [](GLFWwindow* wnd) { @@ -741,8 +751,7 @@ void sys_video_c::GetRelativeCursor(int& x, int& y) if (!initialised) return; double xpos, ypos; glfwGetCursorPos(wnd, &xpos, &ypos); - double sx = vid.size[0] > 0 ? (double)vid.fbSize[0] / vid.size[0] : 1.0; - double sy = vid.size[1] > 0 ? (double)vid.fbSize[1] / vid.size[1] : 1.0; + auto [sx, sy] = CursorScale(vid); x = (int)floor(xpos * sx); y = (int)floor(ypos * sy); } @@ -750,9 +759,8 @@ void sys_video_c::GetRelativeCursor(int& x, int& y) void sys_video_c::SetRelativeCursor(int x, int y) { if (!initialised) return; - double sx = vid.fbSize[0] > 0 ? (double)vid.size[0] / vid.fbSize[0] : 1.0; - double sy = vid.fbSize[1] > 0 ? (double)vid.size[1] / vid.fbSize[1] : 1.0; - glfwSetCursorPos(wnd, x * sx, y * sy); + auto [sx, sy] = CursorScale(vid); + glfwSetCursorPos(wnd, x / sx, y / sy); } bool sys_video_c::IsCursorOverWindow() diff --git a/macos/launcher.c b/macos/launcher.c index b22af65..b94b8b0 100644 --- a/macos/launcher.c +++ b/macos/launcher.c @@ -2,12 +2,19 @@ #include #include #include +#include #include extern int RunLuaFileAsWin(int argc, char **argv); int main(int argc, char **argv) { + char restartMarker[128]; + snprintf(restartMarker, sizeof(restartMarker), + "/tmp/org.pathofbuilding.simplegraphic-smoke-restart-%d", getpid()); + unlink(restartMarker); + setenv("SIMPLEGRAPHIC_SMOKE_RESTART_MARKER", restartMarker, 1); + char executablePath[4096]; uint32_t size = sizeof(executablePath); if (_NSGetExecutablePath(executablePath, &size) == 0) { diff --git a/macos/smoke/Launch.lua b/macos/smoke/Launch.lua index 6f81b95..add2e62 100644 --- a/macos/smoke/Launch.lua +++ b/macos/smoke/Launch.lua @@ -1,6 +1,6 @@ #@ SimpleGraphic -local restartMarker = "/tmp/org.pathofbuilding.simplegraphic-smoke-restart" +local restartMarker = assert(os.getenv("SIMPLEGRAPHIC_SMOKE_RESTART_MARKER")) local marker = io.open(restartMarker, "r") local restarted = marker ~= nil if marker then @@ -8,7 +8,7 @@ if marker then os.remove(restartMarker) end -local smoke = { frames = 0, restarted = restarted } +local smoke = { frames = 0, restarted = restarted, subscriptPassed = false } SetMainObject(smoke) SetWindowTitle("SimpleGraphic macOS smoke") ConExecute("set vid_mode 8") @@ -36,8 +36,22 @@ function smoke:OnInit() assert(require("lua-utf8")) assert(require("lzip")) assert(require("socket.core")) + return true ]], "", "")) - ConPrintf("SMOKE: LuaJIT, native modules, compression, paths, and localhost socket passed.\n") + ConPrintf("SMOKE: main-state modules, compression, paths, and localhost socket passed.\n") +end + +function smoke:OnSubError(_, errorMessage) + Exit("SMOKE FAILED: subscript error: " .. errorMessage) +end + +function smoke:OnSubFinished(_, passed) + if passed ~= true then + Exit("SMOKE FAILED: subscript returned without success") + return + end + self.subscriptPassed = true + ConPrintf("SMOKE: subscript-state native modules passed.\n") end function smoke:OnFrame() @@ -45,7 +59,7 @@ function smoke:OnFrame() SetDrawColor(0.12, 0.55, 0.82, 1) DrawImage(nil, width / 4, height / 4, width / 2, height / 2) self.frames = self.frames + 1 - if self.frames == 120 then + if self.frames >= 120 and self.subscriptPassed then ConPrintf("SMOKE: ANGLE frame loop passed at %dx%d scale %.2f.\n", width, height, GetScreenScale()) if self.restarted then ConPrintf("SMOKE: restart loop passed; exiting cleanly.\n") diff --git a/macos/verify-bundle.sh b/macos/verify-bundle.sh index ca0b876..a265490 100755 --- a/macos/verify-bundle.sh +++ b/macos/verify-bundle.sh @@ -23,20 +23,32 @@ while IFS= read -r file_path; do fi minos=$(vtool -show-build "$file_path" | awk '/minos/ { print $2; exit }') - case "$minos" in - 13|13.*) ;; - *) echo "unexpected deployment target: $minos" >&2; status=1 ;; - esac - - if otool -L "$file_path" | awk 'NR > 1 { print $1 }' | grep -Evq '^(@rpath/|@loader_path/|@executable_path/|/System/|/usr/lib/)'; then - echo "absolute non-system dependency found" >&2 - otool -L "$file_path" >&2 + if ! awk -v minos="$minos" 'BEGIN { exit !(minos + 0 <= 13) }'; then + echo "unexpected deployment target: $minos" >&2 status=1 fi + + install_name=$(otool -D "$file_path" 2>/dev/null | awk 'NR == 2 { print $1 }') + while IFS= read -r dependency; do + if [ "$dependency" = "$install_name" ]; then + continue + fi + case "$dependency" in + @rpath/*) + if [ ! -e "$bundle/Contents/Frameworks/${dependency#@rpath/}" ]; then + echo "unresolved dependency: $dependency" >&2 + status=1 + fi + ;; + /System/*|/usr/lib/*) ;; + *) echo "non-relocatable dependency: $dependency" >&2; status=1 ;; + esac + done < 1 { print $1 }') +EOF done < Date: Thu, 30 Jul 2026 19:08:31 -0700 Subject: [PATCH 3/3] Finalize macOS smoke artifact validation --- CMakeLists.txt | 31 ++++++++++++++++--------------- engine/system/win/sys_main.cpp | 17 +++++++++++------ macos/README.md | 7 ++++--- macos/launcher.c | 12 +++++++++++- macos/smoke/Launch.lua | 24 +++++++++++++++++++++--- macos/verify-bundle.sh | 2 +- 6 files changed, 64 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e9c3cb4..9a711bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -291,29 +291,24 @@ if (WIN32) endif () if (APPLE) - find_package(Git REQUIRED) + find_program(PATCH_EXECUTABLE patch REQUIRED) + set(LCURL_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/Lua-cURLv3) + file(REMOVE_RECURSE ${LCURL_SOURCE_DIR}) + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/libs/Lua-cURLv3/src + DESTINATION ${LCURL_SOURCE_DIR}) execute_process( - COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR}/libs/Lua-cURLv3 - apply --ignore-whitespace --reverse --check + COMMAND ${PATCH_EXECUTABLE} -l -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patches/Lua-cURLv3-skip-luaL_setfuncs-on-luajit.patch - RESULT_VARIABLE LCURL_PATCHED - OUTPUT_QUIET - ERROR_QUIET + WORKING_DIRECTORY ${LCURL_SOURCE_DIR} + COMMAND_ERROR_IS_FATAL ANY ) - if (NOT LCURL_PATCHED EQUAL 0) - execute_process( - COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR}/libs/Lua-cURLv3 - apply --ignore-whitespace - ${CMAKE_CURRENT_SOURCE_DIR}/patches/Lua-cURLv3-skip-luaL_setfuncs-on-luajit.patch - COMMAND_ERROR_IS_FATAL ANY - ) - endif () +else () + set(LCURL_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/Lua-cURLv3) endif () # lcurl module -set(LCURL_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/Lua-cURLv3) file(GLOB LCURL_SOURCES ${LCURL_SOURCE_DIR}/src/**.c) add_library(lcurl SHARED ${LCURL_SOURCES}) @@ -431,6 +426,12 @@ if (APPLE) ) target_link_libraries(SimpleGraphicSmoke PRIVATE SimpleGraphic) add_dependencies(SimpleGraphicSmoke lcurl lua-utf8 luasocket lzip) + set_property(TARGET SimpleGraphicSmoke APPEND PROPERTY LINK_DEPENDS + "$" + "$" + "$" + "$" + ) set_target_properties(SimpleGraphicSmoke PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "@executable_path/../Frameworks" diff --git a/engine/system/win/sys_main.cpp b/engine/system/win/sys_main.cpp index 468948b..7291adb 100644 --- a/engine/system/win/sys_main.cpp +++ b/engine/system/win/sys_main.cpp @@ -615,11 +615,7 @@ std::tuple, std::optional> Fin sys_main_c::sys_main_c() : heldKeyState(KEY_SCROLL + 1, (uint8_t)0) { -#ifdef _WIN64 - x64 = true; -#else - x64 = false; -#endif + x64 = sizeof(void*) == 8; #ifdef _DEBUG debug = true; #else @@ -656,7 +652,12 @@ bool sys_main_c::Run(int argc, char** argv) core = core_IMain::GetHandle(this); // Print some handy information - con->Printf(CFG_VERSION" %s %s, built " __DATE__ "\n", x64? "x64":"x86", debug? "Debug":"Release"); +#if defined(__aarch64__) || defined(_M_ARM64) + const char* architecture = "arm64"; +#else + const char* architecture = x64 ? "x64" : "x86"; +#endif + con->Printf(CFG_VERSION" %s %s, built " __DATE__ "\n", architecture, debug? "Debug":"Release"); if (debuggerRunning) { con->Printf("Debugger is present.\n"); } @@ -721,7 +722,9 @@ bool sys_main_c::Run(int argc, char** argv) #endif if (exitMsg) { +#ifdef _WIN32 exitFlag = false; +#endif video->SetVisible(false); conWin->SetVisible(true); if (exitMsg) { @@ -729,9 +732,11 @@ bool sys_main_c::Run(int argc, char** argv) FreeString(exitMsg); exitMsg = NULL; } +#ifdef _WIN32 while (exitFlag == false) { Sleep(50); } +#endif } initialised = false; diff --git a/macos/README.md b/macos/README.md index 6899d1c..64af835 100644 --- a/macos/README.md +++ b/macos/README.md @@ -47,6 +47,7 @@ macOS 13, absolute non-system dependencies, and invalid ad-hoc signatures. The smoke app proves the native host and packaging seam. It does not yet prove bitmap fonts, PNG/WebP and DDS/BC fixture rendering, screenshots, clipboard, -URL opening, HTTPS trust, or full Path of Building startup. It also still needs -a run on physical Apple Silicon macOS 13 and a green Windows CI build before it -is ready to hand off to Path of Building integration. +URL opening, HTTPS trust, Retina 1x/2x cursor hit-testing, or full Path of +Building startup. It also still needs a run on physical Apple Silicon macOS 13 +and a green Windows CI build before it is ready to hand off to Path of Building +integration. diff --git a/macos/launcher.c b/macos/launcher.c index b94b8b0..3736221 100644 --- a/macos/launcher.c +++ b/macos/launcher.c @@ -10,9 +10,15 @@ extern int RunLuaFileAsWin(int argc, char **argv); int main(int argc, char **argv) { char restartMarker[128]; + char failureMarker[144]; + char successMarker[144]; snprintf(restartMarker, sizeof(restartMarker), "/tmp/org.pathofbuilding.simplegraphic-smoke-restart-%d", getpid()); + snprintf(failureMarker, sizeof(failureMarker), "%s-failed", restartMarker); + snprintf(successMarker, sizeof(successMarker), "%s-passed", restartMarker); unlink(restartMarker); + unlink(failureMarker); + unlink(successMarker); setenv("SIMPLEGRAPHIC_SMOKE_RESTART_MARKER", restartMarker, 1); char executablePath[4096]; @@ -30,5 +36,9 @@ int main(int argc, char **argv) return RunLuaFileAsWin(argc - 1, argv + 1); } char *defaultArgs[] = { "Launch.lua" }; - return RunLuaFileAsWin(1, defaultArgs); + int result = RunLuaFileAsWin(1, defaultArgs); + int passed = access(successMarker, F_OK) == 0; + unlink(failureMarker); + unlink(successMarker); + return result == 0 && passed ? 0 : 1; } diff --git a/macos/smoke/Launch.lua b/macos/smoke/Launch.lua index add2e62..e68bbf1 100644 --- a/macos/smoke/Launch.lua +++ b/macos/smoke/Launch.lua @@ -14,7 +14,15 @@ SetWindowTitle("SimpleGraphic macOS smoke") ConExecute("set vid_mode 8") ConExecute("set vid_resizable 3") -function smoke:OnInit() +function smoke:Fail(message) + local failureMarker = assert(io.open(restartMarker .. "-failed", "w")) + failureMarker:write(message) + failureMarker:close() + ConPrintf("SMOKE FAILED: %s\n", message) + Exit() +end + +function smoke:RunChecks() RenderInit("DPI_AWARE") SetClearColor(0.04, 0.08, 0.12, 1) @@ -41,13 +49,20 @@ function smoke:OnInit() ConPrintf("SMOKE: main-state modules, compression, paths, and localhost socket passed.\n") end +function smoke:OnInit() + local passed, errorMessage = pcall(self.RunChecks, self) + if not passed then + self:Fail(errorMessage) + end +end + function smoke:OnSubError(_, errorMessage) - Exit("SMOKE FAILED: subscript error: " .. errorMessage) + self:Fail("subscript error: " .. errorMessage) end function smoke:OnSubFinished(_, passed) if passed ~= true then - Exit("SMOKE FAILED: subscript returned without success") + self:Fail("subscript returned without success") return end self.subscriptPassed = true @@ -62,6 +77,9 @@ function smoke:OnFrame() if self.frames >= 120 and self.subscriptPassed then ConPrintf("SMOKE: ANGLE frame loop passed at %dx%d scale %.2f.\n", width, height, GetScreenScale()) if self.restarted then + local successMarker = assert(io.open(restartMarker .. "-passed", "w")) + successMarker:write("passed") + successMarker:close() ConPrintf("SMOKE: restart loop passed; exiting cleanly.\n") Exit() else diff --git a/macos/verify-bundle.sh b/macos/verify-bundle.sh index a265490..24d39fe 100755 --- a/macos/verify-bundle.sh +++ b/macos/verify-bundle.sh @@ -23,7 +23,7 @@ while IFS= read -r file_path; do fi minos=$(vtool -show-build "$file_path" | awk '/minos/ { print $2; exit }') - if ! awk -v minos="$minos" 'BEGIN { exit !(minos + 0 <= 13) }'; then + if [ -z "$minos" ] || ! awk -v minos="$minos" 'BEGIN { exit !(minos + 0 <= 13) }'; then echo "unexpected deployment target: $minos" >&2 status=1 fi