mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-29 15:23:01 -04:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48de4bcb08 | |||
| 0cf130d834 | |||
| 38459a97b3 | |||
| b6b297f1ac | |||
| 4d688a8507 | |||
| 64789aa5fc | |||
| f0e6b88cfd |
@@ -208,9 +208,6 @@ jobs:
|
||||
- name: Build bundled mods
|
||||
run: cmake --build --preset ${{matrix.preset}} --target dusklight_mods
|
||||
|
||||
- name: Stage stripped JNI library
|
||||
run: ANDROID_STAGE_ABIS="${{matrix.abi}}" platforms/android/scripts/stage-jni-libs.sh
|
||||
|
||||
- name: Build APK
|
||||
working-directory: platforms/android
|
||||
run: ./gradlew :app:assembleRelease --rerun-tasks
|
||||
|
||||
@@ -2,40 +2,50 @@ include_guard(GLOBAL)
|
||||
|
||||
get_filename_component(_SYMBOL_MANIFEST_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
|
||||
|
||||
set(_SYMGEN_VERSION "1.3.1")
|
||||
set(_SYMGEN_VERSION "1.3.2")
|
||||
set(_SYMGEN_RELEASE_BASE_URL "https://github.com/encounter/symgen/releases/download/v${_SYMGEN_VERSION}")
|
||||
set(SYMGEN_PATH "" CACHE FILEPATH "Path to a symgen executable; empty downloads the pinned release")
|
||||
mark_as_advanced(SYMGEN_PATH)
|
||||
|
||||
function(symgen_host_asset out_name)
|
||||
function(symgen_host_asset out_name out_hash)
|
||||
string(TOLOWER "${CMAKE_HOST_SYSTEM_PROCESSOR}" _host_processor)
|
||||
set(_asset "")
|
||||
set(_asset_hash "")
|
||||
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
|
||||
if (_host_processor MATCHES "^(arm64|aarch64)$")
|
||||
set(_asset "symgen-macos-arm64")
|
||||
set(_asset_hash "SHA256=0344838d1674df09c17c3eeddcf26eb89c333fdb85dfd78f68adc436070eccbe")
|
||||
elseif (_host_processor MATCHES "^(x86_64|amd64)$")
|
||||
set(_asset "symgen-macos-x86_64")
|
||||
set(_asset_hash "SHA256=ae0674f4a1e9d0dedfa02d35939ac28cdd229276b331c1f507df5df809cbec7e")
|
||||
endif ()
|
||||
elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
if (_host_processor MATCHES "^(aarch64|arm64)$")
|
||||
set(_asset "symgen-linux-aarch64")
|
||||
set(_asset_hash "SHA256=af766de2bfaeb0a06f6d7bc17bb2510b4a9c40f44a56e49bc4a4b798a6223042")
|
||||
elseif (_host_processor MATCHES "^(x86_64|amd64)$")
|
||||
set(_asset "symgen-linux-x86_64")
|
||||
set(_asset_hash "SHA256=ebd62fb9623acc942b6295609e2306f85a73043b0a8a117f2072b761dd08e68f")
|
||||
elseif (_host_processor MATCHES "^(i[3-6]86|x86)$")
|
||||
set(_asset "symgen-linux-i686")
|
||||
set(_asset_hash "SHA256=07780a4513fd29726578efc4ff2d88736b22f407ed821b32a68e35ff1e9af5f4")
|
||||
endif ()
|
||||
elseif (CMAKE_HOST_WIN32)
|
||||
if (_host_processor MATCHES "^(arm64|aarch64)$")
|
||||
set(_asset "symgen-windows-arm64.exe")
|
||||
set(_asset_hash "SHA256=5bb22b4a4a9b5ad45646af411bfb09b8321a732ff3a077eb4c9de1feaef27d2b")
|
||||
elseif (_host_processor MATCHES "^(x86_64|amd64)$")
|
||||
set(_asset "symgen-windows-x86_64.exe")
|
||||
set(_asset_hash "SHA256=1d1ac087f991a96932d108969e15998becfec643e88f3e7d182ca97ebfc6a46f")
|
||||
elseif (_host_processor MATCHES "^(i[3-6]86|x86)$")
|
||||
set(_asset "symgen-windows-x86.exe")
|
||||
set(_asset_hash "SHA256=c113f4cd05f813efe2b1878dbfcf44d6302aee3974271736e0036430b0cced78")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(${out_name} "${_asset}" PARENT_SCOPE)
|
||||
set(${out_hash} "${_asset_hash}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(ensure_symgen required)
|
||||
@@ -54,7 +64,7 @@ function(ensure_symgen required)
|
||||
return()
|
||||
endif ()
|
||||
else ()
|
||||
symgen_host_asset(_asset)
|
||||
symgen_host_asset(_asset _asset_hash)
|
||||
if (_asset STREQUAL "")
|
||||
if (required)
|
||||
message(FATAL_ERROR "symgen: no prebuilt binary for host "
|
||||
@@ -75,7 +85,8 @@ function(ensure_symgen required)
|
||||
file(DOWNLOAD "${_url}" "${_symgen}"
|
||||
TLS_VERIFY ON
|
||||
STATUS _download_status
|
||||
SHOW_PROGRESS)
|
||||
SHOW_PROGRESS
|
||||
EXPECTED_HASH "${_asset_hash}")
|
||||
list(GET _download_status 0 _download_code)
|
||||
if (NOT _download_code EQUAL 0)
|
||||
list(GET _download_status 1 _download_message)
|
||||
@@ -106,6 +117,16 @@ function(setup_symbol_manifest target)
|
||||
endif ()
|
||||
add_dependencies(${target} symgen)
|
||||
|
||||
# Reserve an ELF program-header entry when the linker supports it (mold).
|
||||
# symgen can replace the PT_NULL entry without relocating the table, keeping later post-link tools safe.
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
include(CheckLinkerFlag)
|
||||
check_linker_flag(CXX "LINKER:--spare-program-headers=1" _linker_supports_spare_program_headers)
|
||||
if (_linker_supports_spare_program_headers)
|
||||
target_link_options(${target} PRIVATE "LINKER:--spare-program-headers=1")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
set(_input --pdb "$<TARGET_PDB_FILE:${target}>")
|
||||
else ()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
description = "Dusklight — native PC port of the Twilight Princess decompilation";
|
||||
|
||||
inputs.nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
inputs.self.submodules = true;
|
||||
|
||||
outputs =
|
||||
{ self, nixpkgs }:
|
||||
@@ -58,21 +59,6 @@
|
||||
hasNodPrebuilt = nodPrebuiltInfo ? ${system};
|
||||
|
||||
aurora = builtins.pathExists "${self}/extern/aurora/CMakeLists.txt";
|
||||
needSubmodules = ''
|
||||
dusklight: The aurora submodule is not vendored. Add submodules=1 to build.
|
||||
|
||||
As a flake input:
|
||||
|
||||
dusklight.url = "git+https://github.com/TwilitRealm/dusklight?ref=main&submodules=1";
|
||||
|
||||
nix command:
|
||||
|
||||
nix run 'git+https://github.com/TwilitRealm/dusklight?submodules=1'
|
||||
|
||||
Local checkout:
|
||||
|
||||
nix run '.?submodules=1#dusklight'
|
||||
'';
|
||||
|
||||
dawn = pkgs.fetchzip {
|
||||
url = "https://github.com/encounter/dawn/releases/download/${dawnVersion}/dawn-${dawnInfo.${system}.triple}.tar.gz";
|
||||
@@ -140,6 +126,14 @@
|
||||
JSON = pkgs.nlohmann_json.src;
|
||||
XXHASH = pkgs.xxhash.src;
|
||||
ZSTD = pkgs.zstd.src;
|
||||
|
||||
|
||||
MINIZ = pkgs.fetchzip {
|
||||
url = "https://github.com/richgel999/miniz/releases/download/3.0.2/miniz-3.0.2.zip";
|
||||
hash = "sha256-DXysXkQEmoDAMMg1F8KexkwpXNyiHNzLJqXR9SMEkxk=";
|
||||
stripRoot = false;
|
||||
};
|
||||
|
||||
FMT = pkgs.fetchzip {
|
||||
url = "https://github.com/fmtlib/fmt/archive/refs/tags/12.1.0.tar.gz";
|
||||
hash = "sha256-ZmI1Dv0ZabPlxa02OpERI47jp7zFfjpeWCy1WyuPYZ0=";
|
||||
@@ -165,19 +159,16 @@
|
||||
};
|
||||
|
||||
dusklight =
|
||||
if !aurora then
|
||||
throw needSubmodules
|
||||
else
|
||||
pkgs.stdenv.mkDerivation {
|
||||
pname = "dusklight";
|
||||
version = versionSuffix;
|
||||
src = ./.;
|
||||
pkgs.stdenv.mkDerivation {
|
||||
pname = "dusklight";
|
||||
version = versionSuffix;
|
||||
src = ./.;
|
||||
|
||||
postUnpack = ''
|
||||
chmod -R u+w "$sourceRoot"
|
||||
substituteInPlace "$sourceRoot/extern/aurora/CMakeLists.txt" \
|
||||
--replace-warn "add_subdirectory(tests)" ""
|
||||
'';
|
||||
postUnpack = ''
|
||||
chmod -R u+w "$sourceRoot"
|
||||
substituteInPlace "$sourceRoot/extern/aurora/CMakeLists.txt" \
|
||||
--replace-warn "add_subdirectory(tests)" ""
|
||||
'';
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.cmake
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "dev.twilitrealm.shadow_mod",
|
||||
"name": "[Demo] Dynamic Shadows",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"author": "encounter",
|
||||
"description": "Demo showcasing dynamic shadow maps: re-renders geometry from the sun (or moon) point of view and composites real-time shadows over the world, with screen-space contact shadows for fine detail."
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ ConfigVarHandle g_cvarDebugView = 0;
|
||||
GfxDrawTypeHandle g_drawType = 0;
|
||||
GfxStageHookHandle g_sceneBeginHook = 0;
|
||||
GfxStageHookHandle g_sceneAfterTerrainHook = 0;
|
||||
GfxStageHookHandle g_sceneAfterOpaqueHook = 0;
|
||||
GfxStageHookHandle g_frameBeforeHudHook = 0;
|
||||
UiWindowHandle g_controlsWindow = 0;
|
||||
ResourceBuffer g_shaderSource = RESOURCE_BUFFER_INIT;
|
||||
@@ -749,8 +750,8 @@ void render_shadow_map(
|
||||
g_mapPass.ready = true;
|
||||
}
|
||||
|
||||
// Game thread, after the full 3D scene: deferred composite.
|
||||
void on_frame_before_hud(ModContext*, const GfxStageContext*, void*) {
|
||||
// Game thread, after opaque scene draws and before translucent/fog overlays: deferred composite.
|
||||
void on_scene_after_opaque(ModContext*, const GfxStageContext*, void*) {
|
||||
const int64_t debugMode = get_debug_mode();
|
||||
restore_actual_light_debug();
|
||||
|
||||
@@ -797,7 +798,7 @@ void on_frame_before_hud(ModContext*, const GfxStageContext*, void*) {
|
||||
uniforms.inv_size[0] = 1.0f / uniforms.size[0];
|
||||
uniforms.inv_size[1] = 1.0f / uniforms.size[1];
|
||||
uniforms.edge_fade_width =
|
||||
static_cast<float>(std::clamp<int64_t>(get_int_option(g_cvarEdgeFadeWidth, 32), 0, 128));
|
||||
static_cast<float>(std::clamp<int64_t>(get_int_option(g_cvarEdgeFadeWidth, 32), 0, 256));
|
||||
uniforms.strength =
|
||||
mapPass.fade *
|
||||
static_cast<float>(std::clamp<int64_t>(get_int_option(g_cvarStrength, 45), 0, 100)) /
|
||||
@@ -817,6 +818,11 @@ void on_frame_before_hud(ModContext*, const GfxStageContext*, void*) {
|
||||
svc_gfx->push_draw(mod_ctx, g_drawType, &payload, sizeof(payload));
|
||||
}
|
||||
|
||||
// Frame tail hook: only needed to restore light-view debug camera state before HUD.
|
||||
void on_frame_before_hud(ModContext*, const GfxStageContext*, void*) {
|
||||
restore_actual_light_debug();
|
||||
}
|
||||
|
||||
void add_control(UiElementHandle pane, const UiControlDesc& desc) {
|
||||
svc_ui->pane_add_control(mod_ctx, pane, &desc, nullptr);
|
||||
}
|
||||
@@ -873,7 +879,7 @@ ModResult build_controls_tab(
|
||||
"dynamic shadows. This can be expensive.");
|
||||
add_number(left, "Coverage", g_cvarBoxRadius, 1000, 20000, 500, nullptr,
|
||||
"Radius of the shadowed area around the camera, in world units. Smaller is sharper.");
|
||||
add_number(left, "Fade Out", g_cvarEdgeFadeWidth, 0, 128, 32, " texels",
|
||||
add_number(left, "Fade Out", g_cvarEdgeFadeWidth, 0, 256, 32, " texels",
|
||||
"Fade out shadows gradually near the edge of the coverage area.");
|
||||
|
||||
svc_ui->pane_add_section(mod_ctx, left, "Appearance");
|
||||
@@ -1000,7 +1006,7 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
result = register_int_option("edgeFadeWidth", 32, g_cvarEdgeFadeWidth, error);
|
||||
result = register_int_option("edgeFadeWidth", 128, g_cvarEdgeFadeWidth, error);
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
@@ -1041,6 +1047,12 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
|
||||
{
|
||||
return mods::set_error(error, MOD_ERROR, "failed to register stage hook");
|
||||
}
|
||||
stageDesc.callback = on_scene_after_opaque;
|
||||
if (svc_gfx->register_stage_hook(
|
||||
mod_ctx, GFX_STAGE_SCENE_AFTER_OPAQUE, &stageDesc, &g_sceneAfterOpaqueHook) != MOD_OK)
|
||||
{
|
||||
return mods::set_error(error, MOD_ERROR, "failed to register stage hook");
|
||||
}
|
||||
stageDesc.callback = on_frame_before_hud;
|
||||
if (svc_gfx->register_stage_hook(
|
||||
mod_ctx, GFX_STAGE_FRAME_BEFORE_HUD, &stageDesc, &g_frameBeforeHudHook) != MOD_OK)
|
||||
@@ -1099,7 +1111,8 @@ MOD_EXPORT ModResult mod_shutdown(ModError*) {
|
||||
g_cvarStrength = 0;
|
||||
g_cvarPcf = g_cvarBias = g_cvarBoxRadius = g_cvarEdgeFadeWidth = g_cvarContactShadows =
|
||||
g_cvarDebugView = 0;
|
||||
g_drawType = g_sceneBeginHook = g_sceneAfterTerrainHook = g_frameBeforeHudHook = 0;
|
||||
g_drawType = g_sceneBeginHook = g_sceneAfterTerrainHook = g_sceneAfterOpaqueHook =
|
||||
g_frameBeforeHudHook = 0;
|
||||
g_controlsWindow = 0;
|
||||
g_mapPass = {};
|
||||
g_sceneCamera.valid = false;
|
||||
|
||||
@@ -21,26 +21,9 @@ export JAVA_HOME="/usr/lib/jvm/java-17-openjdk"
|
||||
```bash
|
||||
cmake --preset android-arm64
|
||||
cmake --build --preset android-arm64
|
||||
|
||||
cmake --preset android-x86_64
|
||||
cmake --build --preset android-x86_64
|
||||
```
|
||||
|
||||
These builds produce:
|
||||
|
||||
- `build/android-arm64/Binaries/libmain.so`
|
||||
- `build/android-x86_64/Binaries/libmain.so`
|
||||
|
||||
## Stage Libraries Into APK Project
|
||||
|
||||
```bash
|
||||
./android/scripts/stage-jni-libs.sh
|
||||
```
|
||||
|
||||
This copies:
|
||||
|
||||
- `libmain.so` -> `android/app/src/main/jniLibs/arm64-v8a/`
|
||||
- `libmain.so` -> `android/app/src/main/jniLibs/x86_64/`
|
||||
This build produces `build/android-arm64/libmain.so`
|
||||
|
||||
## Refresh SDL Java Shim (Optional)
|
||||
|
||||
|
||||
@@ -6,23 +6,107 @@ def versionNameStr = (System.getenv("DUSK_VERSION") ?: "v0.1.0").replaceFirst("^
|
||||
def versionCodeInt = (System.getenv("DUSK_VERSION_CODE") ?: "100000").toInteger()
|
||||
|
||||
def duskRepoDir = rootProject.projectDir.parentFile.parentFile
|
||||
def duskGeneratedAssetsDir = layout.buildDirectory.dir('generated/assets/dusklight').get().asFile
|
||||
def androidNativeBuildDir = new File(duskRepoDir, 'build/android-arm64')
|
||||
def nativeLibrary = new File(androidNativeBuildDir, 'libmain.so')
|
||||
def stageStripValue = providers.gradleProperty('ANDROID_STAGE_STRIP')
|
||||
.orElse(providers.gradleProperty('androidStageStrip'))
|
||||
.orElse(providers.environmentVariable('ANDROID_STAGE_STRIP'))
|
||||
.orElse('1')
|
||||
def androidHome = {
|
||||
def sdkPath = System.getenv('ANDROID_HOME')
|
||||
if (!sdkPath) {
|
||||
throw new GradleException('ANDROID_HOME is not available')
|
||||
}
|
||||
def sdkDir = new File(sdkPath)
|
||||
if (!sdkDir.isDirectory()) {
|
||||
throw new GradleException("ANDROID_HOME points to a missing directory: ${sdkDir}")
|
||||
}
|
||||
sdkDir
|
||||
}.memoize()
|
||||
|
||||
def androidNdkVersion = {
|
||||
def ndkVersion = System.getenv('ANDROID_NDK_VERSION')
|
||||
if (!ndkVersion) {
|
||||
throw new GradleException('ANDROID_NDK_VERSION is not available')
|
||||
}
|
||||
ndkVersion
|
||||
}.memoize()
|
||||
|
||||
def androidNdkDir = {
|
||||
def ndkDir = new File(androidHome(), "ndk/${androidNdkVersion()}")
|
||||
if (!new File(ndkDir, 'build/cmake/android.toolchain.cmake').isFile()) {
|
||||
throw new GradleException(
|
||||
"Android NDK ${androidNdkVersion()} is missing or invalid at ${ndkDir}"
|
||||
)
|
||||
}
|
||||
ndkDir
|
||||
}.memoize()
|
||||
|
||||
def llvmPrebuiltDir = {
|
||||
def prebuiltRoot = new File(androidNdkDir(), 'toolchains/llvm/prebuilt')
|
||||
def prebuiltDir = (prebuiltRoot.listFiles()?.findAll { it.isDirectory() } ?: [])
|
||||
.sort { it.name }
|
||||
.find { candidate ->
|
||||
new File(
|
||||
candidate,
|
||||
'sysroot/usr/lib/aarch64-linux-android/libc++_shared.so'
|
||||
).isFile()
|
||||
}
|
||||
if (!prebuiltDir) {
|
||||
throw new GradleException("Cannot find NDK libc++_shared.so under ${prebuiltRoot}")
|
||||
}
|
||||
prebuiltDir
|
||||
}.memoize()
|
||||
|
||||
def stlLibrary = {
|
||||
def library = new File(
|
||||
llvmPrebuiltDir(),
|
||||
'sysroot/usr/lib/aarch64-linux-android/libc++_shared.so'
|
||||
)
|
||||
if (!library.isFile()) {
|
||||
throw new GradleException("libc++_shared.so is missing")
|
||||
}
|
||||
library
|
||||
}.memoize()
|
||||
|
||||
def stagedJniLibsDir = layout.buildDirectory.dir('generated/jniLibs/dusklight')
|
||||
|
||||
def stageJniLibs = tasks.register('stageJniLibs', Sync) {
|
||||
group = 'build'
|
||||
from(nativeLibrary) {
|
||||
rename { 'libmain.so' }
|
||||
into 'arm64-v8a'
|
||||
}
|
||||
from(providers.provider { stlLibrary() }) {
|
||||
rename { 'libc++_shared.so' }
|
||||
into 'arm64-v8a'
|
||||
}
|
||||
into(stagedJniLibsDir)
|
||||
|
||||
doFirst {
|
||||
if (!nativeLibrary.isFile()) {
|
||||
throw new GradleException("Native library is missing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def duskGeneratedAssetsDir = layout.buildDirectory.dir('generated/assets/dusklight')
|
||||
def syncDuskAssets = tasks.register('syncDuskAssets', Sync) {
|
||||
from(new File(duskRepoDir, 'res')) {
|
||||
into 'res'
|
||||
exclude '**/.DS_Store'
|
||||
}
|
||||
// Staged by platforms/android/scripts/stage-jni-libs.sh
|
||||
from(new File(projectDir, 'src/main/bundled_mods')) {
|
||||
from(new File(androidNativeBuildDir, 'bundled_mods')) {
|
||||
into 'mods'
|
||||
include '*.dusk'
|
||||
}
|
||||
into duskGeneratedAssetsDir
|
||||
into(duskGeneratedAssetsDir)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'dev.twilitrealm.dusk'
|
||||
compileSdk 36
|
||||
ndkVersion androidNdkVersion()
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'dev.twilitrealm.dusk'
|
||||
@@ -44,16 +128,24 @@ android {
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['src/main/jniLibs']
|
||||
jniLibs.srcDirs = [stagedJniLibsDir]
|
||||
assets.srcDirs = [duskGeneratedAssetsDir]
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
if (stageStripValue.get() == '0') {
|
||||
keepDebugSymbols += '**/libmain.so'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include 'arm64-v8a', 'x86_64'
|
||||
include 'arm64-v8a'
|
||||
universalApk false
|
||||
}
|
||||
}
|
||||
@@ -67,9 +159,6 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
tasks.configureEach { task ->
|
||||
if ((task.name.startsWith('merge') && task.name.endsWith('Assets')) ||
|
||||
task.name.toLowerCase().contains('lint')) {
|
||||
task.dependsOn(syncDuskAssets)
|
||||
}
|
||||
tasks.named('preBuild').configure {
|
||||
dependsOn(stageJniLibs, syncDuskAssets)
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
APP_DIR="$ROOT_DIR/platforms/android/app/src/main/jniLibs"
|
||||
ANDROID_HOME_DIR="${ANDROID_HOME:-$HOME/Android/Sdk}"
|
||||
ANDROID_NDK_VER="${ANDROID_NDK_VERSION:-}"
|
||||
ANDROID_STAGE_ABIS="${ANDROID_STAGE_ABIS:-arm64-v8a x86_64}"
|
||||
ANDROID_STAGE_STRIP="${ANDROID_STAGE_STRIP:-1}"
|
||||
STRIP_TOOL=""
|
||||
|
||||
if [[ -z "$ANDROID_NDK_VER" ]] && [[ -d "$ANDROID_HOME_DIR/ndk" ]]; then
|
||||
ANDROID_NDK_VER="$(ls -1 "$ANDROID_HOME_DIR/ndk" | sort -V | tail -n 1)"
|
||||
fi
|
||||
|
||||
if [[ -n "$ANDROID_NDK_VER" ]]; then
|
||||
case "$(uname -s)" in
|
||||
Darwin) HOST_TAG="darwin-x86_64" ;;
|
||||
Linux) HOST_TAG="linux-x86_64" ;;
|
||||
*) HOST_TAG="" ;;
|
||||
esac
|
||||
|
||||
PREBUILT_DIR="$ANDROID_HOME_DIR/ndk/$ANDROID_NDK_VER/toolchains/llvm/prebuilt"
|
||||
if [[ -n "$HOST_TAG" && -x "$PREBUILT_DIR/$HOST_TAG/bin/llvm-strip" ]]; then
|
||||
STRIP_TOOL="$PREBUILT_DIR/$HOST_TAG/bin/llvm-strip"
|
||||
else
|
||||
for candidate in "$PREBUILT_DIR"/*/bin/llvm-strip; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
STRIP_TOOL="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
copy_lib() {
|
||||
local abi="$1"
|
||||
local src="$2"
|
||||
local dst_dir="$APP_DIR/$abi"
|
||||
local dst="$dst_dir/libmain.so"
|
||||
local tmp="$dst_dir/.libmain.so.$$"
|
||||
if [[ ! -f "$src" ]]; then
|
||||
echo "Missing native library for $abi: $src" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$dst_dir"
|
||||
cp -f "$src" "$tmp"
|
||||
if [[ "$ANDROID_STAGE_STRIP" != "0" ]] && [[ -n "$STRIP_TOOL" ]]; then
|
||||
"$STRIP_TOOL" --strip-unneeded "$tmp"
|
||||
mv -f "$tmp" "$dst"
|
||||
echo "Stripped and staged $src -> $dst"
|
||||
else
|
||||
mv -f "$tmp" "$dst"
|
||||
echo "Staged $src -> $dst (strip disabled or strip tool unavailable)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Drop any previously staged ABI directories to avoid stale APK contents.
|
||||
rm -rf "$APP_DIR/x86" "$APP_DIR/arm64-v8a" "$APP_DIR/x86_64"
|
||||
|
||||
for abi in $ANDROID_STAGE_ABIS; do
|
||||
case "$abi" in
|
||||
arm64-v8a)
|
||||
src="$ROOT_DIR/build/android-arm64/libmain.so"
|
||||
triple="aarch64-linux-android"
|
||||
;;
|
||||
x86_64)
|
||||
src="$ROOT_DIR/build/android-x86_64/libmain.so"
|
||||
triple="x86_64-linux-android"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
copy_lib "$abi" "$src"
|
||||
if [[ -n "$STRIP_TOOL" ]]; then
|
||||
stl="$(dirname "$STRIP_TOOL")/../sysroot/usr/lib/$triple/libc++_shared.so"
|
||||
if [[ -f "$stl" ]]; then
|
||||
cp -f "$stl" "$APP_DIR/$abi/libc++_shared.so"
|
||||
echo "Staged $stl -> $APP_DIR/$abi/libc++_shared.so"
|
||||
else
|
||||
echo "Missing libc++_shared.so for $abi at $stl" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Cannot stage libc++_shared.so for $abi (NDK not found)" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Stage bundled mod packages into the app's assets source dir.
|
||||
MODS_STAGING_DIR="$ROOT_DIR/platforms/android/app/src/main/bundled_mods"
|
||||
rm -rf "$MODS_STAGING_DIR"
|
||||
mkdir -p "$MODS_STAGING_DIR"
|
||||
for abi in $ANDROID_STAGE_ABIS; do
|
||||
case "$abi" in
|
||||
arm64-v8a) build_dir="$ROOT_DIR/build/android-arm64" ;;
|
||||
x86_64) build_dir="$ROOT_DIR/build/android-x86_64" ;;
|
||||
esac
|
||||
[[ -d "$build_dir/bundled_mods" ]] || continue
|
||||
for pkg in "$build_dir/bundled_mods"/*.dusk; do
|
||||
[[ -f "$pkg" ]] || continue
|
||||
name="$(basename "$pkg")"
|
||||
if [[ ! -f "$MODS_STAGING_DIR/$name" ]]; then
|
||||
cp -f "$pkg" "$MODS_STAGING_DIR/$name"
|
||||
echo "Staged bundled mod $pkg"
|
||||
else
|
||||
stage_dir="$build_dir/mods/${name%.dusk}/${name%.dusk}_stage"
|
||||
(cd "$stage_dir" && zip -q -r "$MODS_STAGING_DIR/$name" lib)
|
||||
echo "Appended $abi libraries to bundled mod $name"
|
||||
fi
|
||||
done
|
||||
done
|
||||
@@ -139,6 +139,14 @@ struct NamedHook<Name, R(A...)> : HookImpl<detail::NameTag<Name>, R, A...> {};
|
||||
* leading underscore) or the demangled qualified display name; overloaded display names are
|
||||
* ambiguous and need the mangled form.
|
||||
*/
|
||||
#if defined(__GNUC__) && !defined(__clang__) && defined(__ELF__)
|
||||
#define DEFINE_HOOK(target, alias) \
|
||||
MOD_META_RECORD static constinit auto mod_meta_hook_##alias = \
|
||||
::mods::detail::make_local_hook_record<(target), ::mods::FixedString{#target}>(); \
|
||||
struct alias : ::mods::Hook<(target)> { \
|
||||
static void* resolved_target() { return mod_meta_hook_##alias.resolved; } \
|
||||
}
|
||||
#else
|
||||
#define DEFINE_HOOK(target, alias) \
|
||||
[[maybe_unused]] static const void* const mod_meta_hook_##alias = \
|
||||
&::mods::detail::HookRecordFor<(target), ::mods::FixedString{#target}>::Holder::record; \
|
||||
@@ -148,6 +156,7 @@ struct NamedHook<Name, R(A...)> : HookImpl<detail::NameTag<Name>, R, A...> {};
|
||||
::mods::FixedString{#target}>::Holder::record.resolved; \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
|
||||
#define DEFINE_HOOK_SYMBOL(name, sig, alias) \
|
||||
MOD_META_RECORD static constinit auto mod_meta_hook_##alias = \
|
||||
|
||||
@@ -241,6 +241,48 @@ consteval auto make_hook_mem_names() {
|
||||
return r;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__) && defined(__ELF__)
|
||||
/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41091 prevents inline static template members from
|
||||
* sharing an explicit ELF section with ordinary variables. GCC can instead constant-evaluate a
|
||||
* file-local record at each DEFINE_HOOK. */
|
||||
template <auto Target>
|
||||
void materialize_hook_mem(unsigned char* outPmf) {
|
||||
const auto target = Target;
|
||||
std::memcpy(outPmf, &target, sizeof(target));
|
||||
}
|
||||
|
||||
template <auto Target, FixedString Disp>
|
||||
consteval auto make_local_hook_record() {
|
||||
using F = decltype(Target);
|
||||
if constexpr (std::is_member_function_pointer_v<F>) {
|
||||
constexpr auto names = make_hook_mem_names<Target, Disp>();
|
||||
static_assert(sizeof(F) <= MOD_META_HOOK_MEM_EXT_CAPACITY,
|
||||
"unsupported pointer-to-member representation");
|
||||
if constexpr (sizeof(F) > MOD_META_HOOK_MEM_CAPACITY) {
|
||||
HookMemExtRecord<names.len> record = {
|
||||
{sizeof(HookMemExtRecord<names.len>), MOD_META_HOOK_MEM_EXT, 0}, sizeof(F),
|
||||
materialize_hook_mem<Target>, nullptr, {}};
|
||||
for (size_t i = 0; i < names.len; ++i) {
|
||||
record.names[i] = names.chars[i];
|
||||
}
|
||||
return record;
|
||||
} else {
|
||||
HookMemRecord<F, names.len> record = {
|
||||
{sizeof(HookMemRecord<F, names.len>), MOD_META_HOOK_MEM, 0}, 0, {Target}, nullptr,
|
||||
{}};
|
||||
for (size_t i = 0; i < names.len; ++i) {
|
||||
record.names[i] = names.chars[i];
|
||||
}
|
||||
return record;
|
||||
}
|
||||
} else {
|
||||
static_assert(std::is_pointer_v<F> && std::is_function_v<std::remove_pointer_t<F>>,
|
||||
"hook target must be a function or member function");
|
||||
return HookFnRecord<F>{{sizeof(HookFnRecord<F>), MOD_META_HOOK_FN, 0}, 0, Target, nullptr};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* MSVC constant-evaluates a compact pointer-to-member only when every other operand in the
|
||||
* initializer is a literal: no consteval calls, constexpr-object copies, or default member
|
||||
|
||||
Reference in New Issue
Block a user