mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-24 05:46:51 -04:00
Mods: Rework mod linking
Apple: mods are now MH_BUNDLE (.so) linked with -bundle_loader, replacing the deprecated (on iOS) -undefined dynamic_lookup. Windows: clang-cl mods link dusklight.exe directly; lld's mingw driver synthesizes imports from the export table. cl still requires the import library.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
get_filename_component(_dir "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
|
||||
|
||||
# Android mod linking: symgen scans and filters the game's exports, generating a version script used for the executable
|
||||
# link step, and generates a shared object stub that mods can link against without having to build the whole game.
|
||||
function(setup_android_exports target)
|
||||
include("${_dir}/SymbolManifest.cmake")
|
||||
ensure_symgen(TRUE)
|
||||
add_dependencies(${target} symgen)
|
||||
|
||||
set(_rsp_lines "$<TARGET_OBJECTS:${target}>")
|
||||
foreach (_lib IN LISTS JSYSTEM_LIBRARIES)
|
||||
list(APPEND _rsp_lines "$<TARGET_FILE:${_lib}>")
|
||||
endforeach ()
|
||||
list(JOIN _rsp_lines "\n" _rsp_content)
|
||||
set(_rsp "${CMAKE_BINARY_DIR}/dusklight_exports_input.rsp")
|
||||
file(GENERATE OUTPUT "${_rsp}" CONTENT "${_rsp_content}")
|
||||
|
||||
set(_sdk_args)
|
||||
foreach (_lib aurora_card aurora_core aurora_dvd aurora_gd aurora_gx aurora_mtx
|
||||
aurora_os aurora_pad aurora_si aurora_vi)
|
||||
if (TARGET ${_lib})
|
||||
list(APPEND _sdk_args --sdk-lib "$<TARGET_FILE:${_lib}>")
|
||||
endif ()
|
||||
endforeach ()
|
||||
if (TARGET dawn::webgpu_dawn)
|
||||
get_target_property(_dawn_type dawn::webgpu_dawn TYPE)
|
||||
if (_dawn_type STREQUAL "STATIC_LIBRARY")
|
||||
list(APPEND _sdk_args --sdk-lib "$<TARGET_FILE:dawn::webgpu_dawn>")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(_vscript "${CMAKE_BINARY_DIR}/dusklight_exports.ver")
|
||||
add_custom_command(TARGET ${target} PRE_LINK
|
||||
# TODO: src/dusk/ is NOT excluded: inline code in game headers
|
||||
# currently call into it (e.g. dusk::frame_interp::lookup_replacement).
|
||||
COMMAND "${SYMGEN_EXE}" exports
|
||||
--rsp "${_rsp}"
|
||||
--out "${_vscript}"
|
||||
--format version-script
|
||||
--exclude cmake_pch
|
||||
--exclude miniz
|
||||
--exclude asan_options
|
||||
# Resolved from the Java side; the SDL ones live in the statically-linked
|
||||
# SDL archive, outside the provenance scan.
|
||||
--extra-sym JNI_OnLoad
|
||||
--extra-sym SDL_main
|
||||
--extra-sym "Java_*"
|
||||
${_sdk_args}
|
||||
COMMENT "Generating dusklight exports"
|
||||
VERBATIM)
|
||||
target_link_options(${target} PRIVATE "-Wl,--version-script=${_vscript}")
|
||||
|
||||
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _arch)
|
||||
if (_arch STREQUAL "aarch64")
|
||||
set(_machine "arm64")
|
||||
else ()
|
||||
set(_machine "amd64")
|
||||
endif ()
|
||||
|
||||
set(_stub "${CMAKE_BINARY_DIR}/stub-android-${_arch}.so")
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND "${SYMGEN_EXE}" stub -f elf "$<TARGET_FILE:${target}>" -o "${_stub}"
|
||||
--soname "$<TARGET_FILE_NAME:${target}>" --machine "${_machine}"
|
||||
BYPRODUCTS "${_stub}"
|
||||
COMMENT "Generating dusklight link stub"
|
||||
VERBATIM)
|
||||
install(FILES "${_stub}" DESTINATION sdk)
|
||||
endfunction()
|
||||
@@ -0,0 +1,91 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
get_filename_component(_dir "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
|
||||
|
||||
# Apple mod linking: symgen scans and filters the game's exports, generating an exports list used for the executable
|
||||
# link step, and generates a MH_EXECUTE Mach-O stub that mods can link against without having to build the whole game.
|
||||
function(setup_apple_exports target)
|
||||
include("${_dir}/SymbolManifest.cmake")
|
||||
ensure_symgen(TRUE)
|
||||
set(_symgen "${SYMGEN_EXE}")
|
||||
add_dependencies(${target} symgen)
|
||||
|
||||
set(_config_subdir "")
|
||||
if (CMAKE_CONFIGURATION_TYPES)
|
||||
set(_config_subdir "$<CONFIG>/")
|
||||
endif ()
|
||||
|
||||
set(_rsp_lines "$<TARGET_OBJECTS:${target}>")
|
||||
foreach (_lib IN LISTS JSYSTEM_LIBRARIES)
|
||||
list(APPEND _rsp_lines "$<TARGET_FILE:${_lib}>")
|
||||
endforeach ()
|
||||
list(JOIN _rsp_lines "\n" _rsp_content)
|
||||
set(_rsp "${CMAKE_BINARY_DIR}/${_config_subdir}dusklight_exports_input.rsp")
|
||||
file(GENERATE OUTPUT "${_rsp}" CONTENT "${_rsp_content}")
|
||||
|
||||
set(_sdk_args)
|
||||
foreach (_lib aurora_card aurora_core aurora_dvd aurora_gd aurora_gx aurora_mtx
|
||||
aurora_os aurora_pad aurora_si aurora_vi)
|
||||
if (TARGET ${_lib})
|
||||
list(APPEND _sdk_args --sdk-lib "$<TARGET_FILE:${_lib}>")
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
# Dawn is linked statically on Apple; mods reach wgpu* through the executable.
|
||||
if (TARGET dawn::webgpu_dawn)
|
||||
get_target_property(_dawn_type dawn::webgpu_dawn TYPE)
|
||||
if (_dawn_type STREQUAL "STATIC_LIBRARY")
|
||||
list(APPEND _sdk_args --sdk-lib "$<TARGET_FILE:dawn::webgpu_dawn>")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(_exp "${CMAKE_BINARY_DIR}/${_config_subdir}dusklight_exports.exp")
|
||||
add_custom_command(TARGET ${target} PRE_LINK
|
||||
# TODO: src/dusk/ is NOT excluded: inline code in game headers
|
||||
# currently call into it (e.g. dusk::frame_interp::lookup_replacement).
|
||||
COMMAND "${_symgen}" exports
|
||||
--rsp "${_rsp}"
|
||||
--out "${_exp}"
|
||||
--exclude cmake_pch
|
||||
--exclude miniz
|
||||
--exclude asan_options
|
||||
${_sdk_args}
|
||||
COMMENT "Generating dusklight exports"
|
||||
VERBATIM)
|
||||
target_link_options(${target} PRIVATE -Xlinker -exported_symbols_list -Xlinker "${_exp}")
|
||||
|
||||
# Generate the stub executable mods link against via -bundle_loader.
|
||||
set(_stub_args)
|
||||
if (IOS)
|
||||
set(_stub_platform "ios")
|
||||
list(APPEND _stub_args --platform ios)
|
||||
elseif (TVOS)
|
||||
set(_stub_platform "tvos")
|
||||
list(APPEND _stub_args --platform tvos)
|
||||
else ()
|
||||
set(_stub_platform "macos")
|
||||
endif ()
|
||||
if (CMAKE_OSX_DEPLOYMENT_TARGET)
|
||||
list(APPEND _stub_args --min-os "${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||
endif ()
|
||||
if (CMAKE_OSX_ARCHITECTURES)
|
||||
set(_archs "${CMAKE_OSX_ARCHITECTURES}")
|
||||
else ()
|
||||
set(_archs "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif ()
|
||||
set(_arch_names "")
|
||||
foreach (_arch IN LISTS _archs)
|
||||
string(TOLOWER "${_arch}" _arch)
|
||||
list(APPEND _stub_args --arch "${_arch}")
|
||||
list(APPEND _arch_names "${_arch}")
|
||||
endforeach ()
|
||||
list(JOIN _arch_names "_" _arch_name)
|
||||
|
||||
set(_stub "${CMAKE_BINARY_DIR}/${_config_subdir}dusklight-stub")
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND "${_symgen}" stub -f macho "${_exp}" -o "${_stub}" ${_stub_args}
|
||||
BYPRODUCTS "${_stub}"
|
||||
COMMENT "Generating dusklight link stub"
|
||||
VERBATIM)
|
||||
install(FILES "${_stub}" DESTINATION sdk RENAME "stub-${_stub_platform}-${_arch_name}")
|
||||
endfunction()
|
||||
+48
-20
@@ -13,14 +13,15 @@ function(_mod_lib_info out_platform_var out_name_var)
|
||||
set(_arch "${CMAKE_OSX_ARCHITECTURES}")
|
||||
endif ()
|
||||
string(TOLOWER "${CMAKE_SYSTEM_NAME}" _platform)
|
||||
if (_platform STREQUAL "darwin")
|
||||
set(_platform "macos")
|
||||
endif ()
|
||||
string(TOLOWER "${_arch}" _arch)
|
||||
if (_arch MATCHES "^(i[3-6]86|x86)$")
|
||||
set(_arch "x86")
|
||||
endif ()
|
||||
if (WIN32)
|
||||
set(_ext ".dll")
|
||||
elseif (APPLE)
|
||||
set(_ext ".dylib")
|
||||
else ()
|
||||
set(_ext ".so")
|
||||
endif ()
|
||||
@@ -62,7 +63,7 @@ function(add_mod target_name)
|
||||
set(_lib_name "")
|
||||
if (ARG_SOURCES)
|
||||
set(_has_lib TRUE)
|
||||
add_library(${target_name} SHARED ${ARG_SOURCES})
|
||||
add_library(${target_name} MODULE ${ARG_SOURCES})
|
||||
_mod_lib_info(_lib_platform _lib_name)
|
||||
set_target_properties(${target_name} PROPERTIES
|
||||
PREFIX ""
|
||||
@@ -94,18 +95,28 @@ function(add_mod target_name)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
# Game symbols resolve against the host executable at dlopen time.
|
||||
target_link_options(${target_name} PRIVATE -undefined dynamic_lookup)
|
||||
if (TARGET dusklight)
|
||||
set(_game_exe "$<TARGET_FILE:dusklight>")
|
||||
add_dependencies(${target_name} dusklight)
|
||||
elseif (DUSK_GAME_EXE)
|
||||
_mod_resolve_source_path(_game_exe "${DUSK_GAME_EXE}")
|
||||
else ()
|
||||
message(FATAL_ERROR "add_mod: DUSK_GAME_EXE is not set (game executable)")
|
||||
endif ()
|
||||
target_link_options(${target_name} PRIVATE
|
||||
-Xlinker -bundle_loader -Xlinker "${_game_exe}")
|
||||
set_property(TARGET ${target_name} APPEND PROPERTY LINK_DEPENDS "${_game_exe}")
|
||||
set_target_properties(${target_name} PROPERTIES
|
||||
BUILD_RPATH "@loader_path"
|
||||
INSTALL_RPATH "@loader_path")
|
||||
elseif (ANDROID)
|
||||
if (TARGET dusklight)
|
||||
target_link_libraries(${target_name} PRIVATE dusklight)
|
||||
elseif (DUSK_GAME_SOLIB)
|
||||
target_link_libraries(${target_name} PRIVATE "${DUSK_GAME_SOLIB}")
|
||||
elseif (DUSK_GAME_EXE)
|
||||
_mod_resolve_source_path(_game_lib "${DUSK_GAME_EXE}")
|
||||
target_link_libraries(${target_name} PRIVATE "${_game_lib}")
|
||||
else ()
|
||||
message(FATAL_ERROR "add_mod: DUSK_GAME_SOLIB is not set (libmain.so)")
|
||||
message(FATAL_ERROR "add_mod: DUSK_GAME_EXE is not set (libmain.so or stub)")
|
||||
endif ()
|
||||
set_target_properties(${target_name} PROPERTIES
|
||||
BUILD_RPATH "$ORIGIN"
|
||||
@@ -116,16 +127,33 @@ function(add_mod target_name)
|
||||
BUILD_RPATH "$ORIGIN"
|
||||
INSTALL_RPATH "$ORIGIN")
|
||||
elseif (WIN32)
|
||||
# Link against the generated import library (game ABI surface). Function calls
|
||||
# resolve through import thunks. Data is toolchain dependent:
|
||||
# - clang-cl: lld's mingw mode auto-imports data references, fixed up at load by
|
||||
# the mod SDK's pseudo-relocation runtime (pseudo_reloc.cpp).
|
||||
# - cl (MSVC): only DUSK_GAME_DATA-annotated data is reachable. Un-annotated
|
||||
# Function calls resolve through import thunks. Data is toolchain dependent:
|
||||
# - clang-cl: lld's mingw driver synthesizes imports straight from the game
|
||||
# executable's export table (no import library needed) and auto-imports data
|
||||
# references, fixed up at load by the mod SDK's pseudo-relocation runtime
|
||||
# (pseudo_reloc.cpp).
|
||||
# - cl (MSVC): link.exe only imports through an import library, so
|
||||
# DUSK_GAME_EXE must point at one (sdk/windows-<arch>.lib or `symgen stub`
|
||||
# output); only DUSK_GAME_DATA-annotated data is reachable. Un-annotated
|
||||
# references fail to link.
|
||||
if (NOT DUSK_GAME_IMPLIB)
|
||||
message(FATAL_ERROR "add_mod: DUSK_GAME_IMPLIB is not set.")
|
||||
if (TARGET dusklight)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(_game_lib "$<TARGET_FILE:dusklight>")
|
||||
add_dependencies(${target_name} dusklight)
|
||||
elseif (DUSK_GAME_IMPLIB)
|
||||
set(_game_lib "${DUSK_GAME_IMPLIB}")
|
||||
else ()
|
||||
message(FATAL_ERROR "add_mod: DUSK_GAME_IMPLIB is not set (see setup_windows_exports)")
|
||||
endif ()
|
||||
elseif (DUSK_GAME_EXE)
|
||||
_mod_resolve_source_path(_game_lib "${DUSK_GAME_EXE}")
|
||||
if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT _game_lib MATCHES "\\.lib$")
|
||||
message(FATAL_ERROR "add_mod: cl must use an import library for DUSK_GAME_EXE")
|
||||
endif ()
|
||||
else ()
|
||||
message(FATAL_ERROR "add_mod: DUSK_GAME_EXE is not set (game executable or import library)")
|
||||
endif ()
|
||||
target_link_libraries(${target_name} PRIVATE "${DUSK_GAME_IMPLIB}")
|
||||
target_link_libraries(${target_name} PRIVATE "${_game_lib}")
|
||||
set_target_properties(${target_name} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
|
||||
target_compile_definitions(${target_name} PRIVATE _ITERATOR_DEBUG_LEVEL=0)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
@@ -266,9 +294,9 @@ endfunction()
|
||||
# user cache).
|
||||
# - Linux: pre-extracted stage dirs into <install>/mods so native libs dlopen in place from
|
||||
# read-only installs.
|
||||
# - macOS: pre-extracted stage dirs into the installed app's Contents/Resources/mods, dylibs
|
||||
# ad-hoc signed in place, then the whole bundle re-signed.
|
||||
# - iOS/tvOS: assets into <app>/mods/<id>, the mod dylib into Frameworks/<id>.dylib,
|
||||
# - macOS: pre-extracted stage dirs into the installed app's Contents/Resources/mods, native
|
||||
# libs ad-hoc signed in place, then the whole bundle re-signed.
|
||||
# - iOS/tvOS: assets into <app>/mods/<id>, the mod library into Frameworks/<id>.so,
|
||||
# and runtime libraries alongside it.
|
||||
# - Android: nothing here; gradle packs ${CMAKE_BINARY_DIR}/bundled_mods into APK assets.
|
||||
function(install_bundled_mods)
|
||||
@@ -299,7 +327,7 @@ function(install_bundled_mods)
|
||||
install(DIRECTORY "${_stage}/" DESTINATION "${_bundle_dir}/mods/${_id}"
|
||||
PATTERN "lib" EXCLUDE)
|
||||
install(PROGRAMS "$<TARGET_FILE:${_target}>"
|
||||
DESTINATION "${_bundle_dir}/Frameworks" RENAME "${_id}.dylib")
|
||||
DESTINATION "${_bundle_dir}/Frameworks" RENAME "${_id}.so")
|
||||
install(DIRECTORY "${_stage}/lib/${_lib_platform}/"
|
||||
DESTINATION "${_bundle_dir}/Frameworks"
|
||||
PATTERN "${_lib_name}" EXCLUDE)
|
||||
|
||||
+5
-6
@@ -7,8 +7,7 @@
|
||||
# add_subdirectory(<dusk>/sdk dusk-sdk EXCLUDE_FROM_ALL)
|
||||
# add_mod(my_mod SOURCES ... MOD_JSON mod.json)
|
||||
#
|
||||
# On Windows, pass -DDUSK_GAME_IMPLIB=<path to sdk/dusklight.lib> from the
|
||||
# matching game release. TODO: auto-download from tag
|
||||
# TODO: auto-download link targets from tag
|
||||
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
|
||||
@@ -38,10 +37,10 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/../extern/aurora/cmake/AuroraDawnProvider.c
|
||||
# Game ABI headers & compile definitions
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GameABIConfig.cmake")
|
||||
|
||||
if (WIN32)
|
||||
set(DUSK_GAME_IMPLIB "" CACHE FILEPATH "Path to the dusklight import library (sdk/dusklight.lib)")
|
||||
if (DUSK_GAME_IMPLIB AND NOT EXISTS "${DUSK_GAME_IMPLIB}")
|
||||
message(FATAL_ERROR "Mod SDK: DUSK_GAME_IMPLIB does not exist: ${DUSK_GAME_IMPLIB}")
|
||||
if (WIN32 OR APPLE OR ANDROID)
|
||||
set(DUSK_GAME_EXE "" CACHE FILEPATH "Game binary or link stub mods link against")
|
||||
if (DUSK_GAME_EXE AND NOT EXISTS "${DUSK_GAME_EXE}")
|
||||
message(FATAL_ERROR "Mod SDK: DUSK_GAME_EXE does not exist: ${DUSK_GAME_EXE}")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
|
||||
@@ -55,13 +55,13 @@ static constexpr std::string_view k_nativePlatform = "ios-arm64"sv;
|
||||
#elif TARGET_OS_TV
|
||||
static constexpr std::string_view k_nativePlatform = "tvos-arm64"sv;
|
||||
#elif defined(__aarch64__)
|
||||
static constexpr std::string_view k_nativePlatform = "darwin-arm64"sv;
|
||||
static constexpr std::string_view k_nativePlatform = "macos-arm64"sv;
|
||||
#elif defined(__x86_64__)
|
||||
static constexpr std::string_view k_nativePlatform = "darwin-x86_64"sv;
|
||||
static constexpr std::string_view k_nativePlatform = "macos-x86_64"sv;
|
||||
#else
|
||||
static constexpr std::string_view k_nativePlatform = ""sv;
|
||||
#endif
|
||||
static constexpr std::string_view k_nativeLibName = "mod.dylib"sv;
|
||||
static constexpr std::string_view k_nativeLibName = "mod.so"sv;
|
||||
#elif defined(__linux__)
|
||||
#if defined(__aarch64__)
|
||||
static constexpr std::string_view k_nativePlatform = "linux-aarch64"sv;
|
||||
@@ -130,7 +130,7 @@ NativeLocateResult locate_native_runtime(ModBundle& bundle) {
|
||||
if (platformEnd != std::string_view::npos) {
|
||||
const auto entryName = libPath.substr(platformEnd + 1);
|
||||
if (entryName.find('/') == std::string_view::npos &&
|
||||
(entryName == "mod.dll"sv || entryName == "mod.dylib"sv || entryName == "mod.so"sv))
|
||||
(entryName == "mod.dll"sv || entryName == "mod.so"sv))
|
||||
{
|
||||
result.anyLibs = true;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@ public:
|
||||
|
||||
#if defined(_WIN32)
|
||||
static constexpr auto LibraryExtension = ".dll";
|
||||
#elif defined(__APPLE__)
|
||||
static constexpr auto LibraryExtension = ".dylib";
|
||||
#else
|
||||
static constexpr auto LibraryExtension = ".so";
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user